Trellised継承

一見単純に見える継承は、多くの場合、変化に耐える複雑な構造になります。 クラス階層は本物の森のように成長します。

継承の目的は、コード(メソッドのセット)を、それが提供し必要とするエンティティプロパティの最小セット(通常はオブジェクト)にバインドすることです。 これにより、コードの再利用、テスト、および分析が簡素化されます。 しかし、プロパティのセットは時間とともに非常に大きくなり、重要な方法で交差し始めます。 そして、ミックスインやその他の多重継承がクラスの構造に現れます。

階層の深さを変更すると問題が発生するため、事前に「依存性注入」について考え、複雑なリファクタリングツールを開発して使用する必要があります。



これをすべて回避することは可能ですか? 試してみる価値があります-メソッドをオブジェクトの特性プロパティ(タグ)のセットに結び付け、これらのセットのネストに従って継承階層を自動的に構築します。



ゲームキャラクターの階層を設計しましょう。 コードの一部はすべてのキャラクターに共通です-それはプロパティの空のセットに関連付けられています。 それらを表示するコードは、異なるバージョンの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に組み込まれておらず、あまり効率的でもありません。



All Articles