継承の目的は、コード(メソッドのセット)を、それが提供し必要とするエンティティプロパティの最小セット(通常はオブジェクト)にバインドすることです。 これにより、コードの再利用、テスト、および分析が簡素化されます。 しかし、プロパティのセットは時間とともに非常に大きくなり、重要な方法で交差し始めます。 そして、ミックスインやその他の多重継承がクラスの構造に現れます。
階層の深さを変更すると問題が発生するため、事前に「依存性注入」について考え、複雑なリファクタリングツールを開発して使用する必要があります。
これをすべて回避することは可能ですか? 試してみる価値があります-メソッドをオブジェクトの特性プロパティ(タグ)のセットに結び付け、これらのセットのネストに従って継承階層を自動的に構築します。
ゲームキャラクターの階層を設計しましょう。 コードの一部はすべてのキャラクターに共通です-それはプロパティの空のセットに関連付けられています。 それらを表示するコードは、異なるバージョンのOpenGLとDirectXのオプションの形式で表示されます。 何かはキャラクターの種族、魔法の能力の存在や種類などに依存します。 文字タグが主要です。 それらは明示的にリストされ、継承されません。 また、実装は、タグのセットに応じて(ネストすることにより)継承されます。 したがって、カンガルーは歩兵から継承されたため、MANPADSから撮影することはできません。
このアプローチのアイデアは、Dmitry Kimによって提案されました。 著者はコードでそれを具体化しなかったので、この省略を修正しようとします。
いつものようにgithubでClojureにこのアプローチを実装します。
この継承メソッドの実装は、ジェネリック関数のClojureシステム(マルチメソッド)の上で行われます。
defmultiで定義された各マルチメソッドには、階層と、引数を階層の要素(または要素の配列)にマップするディスパッチ関数があります。 通常、階層要素はデータ型ですが、階層で「キーワード」と「シンボル」を使用して、必要な型に割り当てられたデータをマークすることもできます。
階層要素の具体的なメソッド実装は、defmetodを使用して指定されます。
次のようになります。
(use 'inheritance.grid) (def grid (make-grid-hierarchy)) (defmulti canFly " " (grid-dispatch1) :hierarchy #'grid) (defmulti canFireball " " (grid-dispatch1) :hierarchy #'grid) (defmulti canFire " " (grid-dispatch1) :hierarchy #'grid) (defmethod canFly (get-grid-node {} #'grid) [p] false) ; (defmethod canFly (get-grid-node {:magic :air} #'grid) [p] true) ; - (defmethod canFly (get-grid-node {:limbs :wings} #'grid) [p] true) ; (defmethod canFireball (get-grid-node {} #'grid) [p] false) ; (defmethod canFireball (get-grid-node {:magic :fire, :limbs :hands} #'grid) [p] (> (:mana p) 0)) ; - , . (defmethod canFire (get-grid-node {} #'grid) [p] false) ; , , (defmethod canFire (get-grid-node {:limbs :hands} #'grid) [p] true) ; (defmethod canFire (get-grid-node {:magic :fire} #'grid) [p] (> (:mana p) 0)) ; (defmethod canFire (get-grid-node {:magic :fire, :limbs :hands} #'grid) [p] true) ; - Clojure , (def mage ((with-grid-node {:magic :fire, :limbs :hands :race :mage} #'grid) {:mana 100, :power 5})) (def barbar ((with-grid-node {:magic :none, :limbs :hands :race :human} #'grid) {:power 500})) (def phoenix ((with-grid-node {:magic :fire, :limbs :wings :race :mage} #'grid) {:mana 200, :power 4})) (def elf ((with-grid-node {:magic :air, :limbs :hands :race :elf} #'grid) {:mana 300, :power 13})) (canFire elf) ; true (canFireball elf) ; false (canFly elf) ; true (canFly mage) ; false (canFire mage) ; true
仕組み:
まず、階層を作成する必要があります-これは通常のClojure階層で、タグのセット(連想配列の形式)を階層に参加するキャラクターにマップするテーブルがあります。 テーブルは最初は空で、階層オブジェクトのメタ情報に保存されています。
(defn make-grid-hierarchy " " [] (let [h (make-hierarchy)] ; (with-meta h (assoc (or (meta h) {}) :grid-hierarchy-cache {})))) ;
タグの各セットは階層に登録する必要があります-シンボルが作成され、階層内の正しい場所に含まれ、このシンボルが見つかるようにテーブル内の対応するエントリが追加されます。 階層内の適切な場所を計算することが、この継承管理方法の基盤です。
(defn register-grid-node " " [ho] (let [nl (get (meta h) :grid-hierarchy-cache {})] (if-let [s (nl o)] ; [hs] ; (let [s (symbol (str o)) ; - hn (reduce (fn [h [tr n]] ; (if (and (subobj? tr o) ; (not (isa? hsn))) ; Clojure , ; (derive hsn) (if (and (subobj? o tr) (not (isa? hns))) ; (derive hns) h))) h nl)] [(with-meta hn ; (assoc (or (meta h) {}) :grid-hierarchy-cache (assoc nl os))) s])))) ;
ここで、一連のタグで定義された特定のラティスノードの型を、この型に属すると思われるデータに関連付ける方法を学習する必要があります。
(defn with-grid-node " , " [nh] (let [s (get-grid-node nh)] (fn [v] (with-meta v (assoc (or (meta v) {}) :grid-node s)))))
ノードのテーブルで検索が繰り返されるのを避けるために、この関数はノードに対応する文字を受け取り、この文字を引数のメタ情報に追加するクロージャーを返します。
ディスパッチ機能は簡単です。
(defn grid-dispatch " " [] (fn [& v] (vec (map (fn [a] (:grid-node (meta a))) v)))) (defn grid-dispatch1 " " [] (fn [v & _] (:grid-node (meta v))))
私はすでにCommon Lispでそのような継承を実装しようとしました 。 しかし、MOPデバイスはわかりません。その実装はCLOSに組み込まれておらず、あまり効率的でもありません。