Lattice inheritance

Inheritance, despite the seeming simplicity often leads to challenging resistance to change of structures. Class hierarchy grow as a real forest.
The purpose of inheritance is to bind the code (set of methods) to the minimum set of properties of an entity (typically an object), which it provides and which it requires. This makes it easier to reuse, test, and code analysis. But the sets of properties will eventually become very large, begin to intersect in a nontrivial way. And in the class structure appear other mixins and multiple inheritance.
To make changes in the depth of the hierarchy becomes problematic, you have to think in advance about "dependency injection", to develop and use sophisticated tools of refactoring.

Is it possible to avoid it? It's worth a try — let methods will be tied to a variety of characteristic properties of an object (tags), and the inheritance hierarchy is built automatically by inclusion of these sets.

Suppose we are developing a hierarchy for game characters. Part of the code is common to all the characters — she's bound to an empty set of properties. The code responsible for displaying them will be presented in the form of options for OpenGL and DirectX different versions. Something will depend on the race of the character, something of the presence and kind of magical powers etc. Tags character primary. They are listed explicitly, and are not inherited. And the implementation is inherited depending on the tag set (for nesting). Thus the ability to shoot from MANPADS will not be a kangaroo, because it is inherited from the marine.

The idea of this approach was proposed by Dmitry Kim. The author did not implement it in code, I'll try to correct this omission.
Implementation of this approach in Clojure, as usual, on github.

The implementation of this method of inheritance is made over the system of generalized functions (multimethods) Clojure.
Every multimethods defined using defmulti, associated hierarchy and here are the function that displays the arguments in the element (or array elements) of the hierarchy. Usually the elements of the hierarchy are data types, but in their hierarchies can be used as "key words" and "symbols", which will be marked with data related to the desired type.
The specific implementation of the method for the hierarchy is specified using defmetod.
It looks like this:
the
(use 'inheritance.grid)
(def grid (make-grid-hierarchy))

(defmulti canFly "the character can fly" (grid-dispatch1) :hierarchy #'grid)
(defmulti canFireball "character can put fairbody" (grid-dispatch1) :hierarchy #'grid)
(defmulti canFire "the character can fire" (grid-dispatch1) :hierarchy #'grid)

(defmethod canFly (get-grid-node {} #'grid) [p] false)
; by default characters do not fly
(defmethod canFly (get-grid-node {:magic :air} #'grid) [p] true)
; with magic air - fly
(defmethod canFly (get-grid-node {:limbs :wings} #'grid) [p] true)
; winged fly

(defmethod canFireball (get-grid-node {} #'grid) [p] false)
; by default characters are not allowed fairbody
(defmethod canFireball (get-grid-node {:magic :fire :limbs :hands} #'grid) [p] (> (:mana p) 0))
; wielding the magic of fire and has hand - allowed, if there is mana.

(defmethod canFire (get-grid-node {} #'grid) [p] false)
; fire, by default, no one is Vlad
(defmethod canFire (get-grid-node {:limbs :hands} #'grid) [p] true)
; handy, can build a fire
(defmethod canFire (get-grid-node {:magic :fire} #'grid) [p] (> (:mana p) 0))
; wielding magic hands have not necessarily
(defmethod canFire (get-grid-node {:magic :fire :limbs :hands} #'grid) [p] true)
; magic and hands compatible - Clojure afraid to mix up the reason for this is the properties

(def mage ((with-grid-node {:magic :fire :limbs :hands :race :mage} #'grid) {:100 mana, :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


How it works:
First you need to create a hierarchy — it would just be a hierarchy of Clojure, with a table displaying the set of tags (in the form of associative array) in Castoldi in the hierarchy symbol. The table is initially empty and stored in the meta information of the object hierarchy.
(let [h (make-hierarchy)] ; this is a standard hierarchy (with-meta h (assoc (or (meta h) {}) :grid-hierarchy-cache {})))) ; but with meta-information about the lattice structure

Each used the same set of tags must be registered in the hierarchy — for it is created and included in the correct place in the hierarchy symbol, and dobavlena corresponding entry in the table, this symbol can be found. Calculating the correct places in the hierarchy — the basis of this control method inheritance.
the
(defn register-grid-node "register a new lattice point in the hierarchy" [h o]
(let [nl (get (meta h) :grid-hierarchy-cache {})]
(if-let [s (nl o)]
; but not if he was already
[h s]
; then return the old hierarchy and node symbol
(let [s (symbol (str o))
new node - create his character
hn (reduce (fn [h [tr n]]
; go through existing nodes
(if (and (subobj? tr o)
; but whether this node is to inherit from our
(not (isa? h s n)))
; Clojure nervously reacts to the attempt to register a connection,
; which she can withdraw
(derive h s n)
(if (and (subobj? o tr) (not (isa? h n s)))
; or our site to inherit from it
(derive h n s)
h)))
h nl)]
[(with-meta hn
; add meta-information about the new node in the hierarchy
(assoc (or (meta h) {})
:grid-hierarchy-cache (assoc nl o s)))
s]))))
; and return together with the symbol of the new node


Now we have to learn to associate the type of a lattice point specified by a set of tags with data that, we believe, belong to this type.
the
(defn with-grid-node "creates a function that adds meta-information about the node to the object" [n h]
(let [s (get-grid-node n h)]
(fn [v]
(with-meta v (assoc (or (meta v) {}) :grid-node s)))))

To avoid repeated searches on host table, this function gets the symbol that corresponds to the node and returns a closure that adds this symbol to the meta-information of its argument.

Functions obtained here are simple.
the
(defn grid-dispatch "Creates a Manager for all method arguments"
[] (fn [&v] (vec (map (fn [a] (:grid-node (meta a))) v))))
(defn grid-dispatch1 "Creates a Manager for the first argument"
[] (fn [v&_] (: grid-node (meta v))))


This inheritance I have already tried to implement in Common Lisp. But the device MOP, I don't know, and that implementation is not built into CLOS and not too effective.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Automatically create Liquibase migrations for PostgreSQL

Vkontakte sync with address book for iPhone. How it was done

What part of the archived web