clojure回避了传统的面向对象式方法,每一个新的情况都创建一个数据类型。clojure倾向于创建一个大的方法库,方法库中只有一个小的类型集合。然而,clojure充分认识到运行时多态在实现灵活的和可扩展的架构的优势。clojure通过Multimethods来实现复杂的运行时多态,该系统支持调度一个或多个参数的类型、值、属性和元数据以及它们之间的关系。
clojure的multimethods由一个dispatching函数和一个或多个方法组成。当multimethod被定义时,使用defmulti,一个dispatching方法必须被提供。这个函数将要作为参数被提供给multimethod,目的是为了产生dispatching值。multimethod这时尝试寻找关联dispatching值或者从dispatching值派生的方法,如果一个函数被defmethod定义,这个函数将被调用。如果找不到的会,则会寻找默认的dispatching值,然后调用它。如果还找不到的话,就会报错。
multimethod的api有
- defmulti创建新的multimethods
- defmethod创建和设置一个新的方法,并关联一个dispatching-value
- remove-method移除一个关联dispatching-value的方法
- prefer-method创建一个可能会出现歧义时调用函数的顺序
派生是有java的继承或者clojure的ad hoc hierarchy系统决定的。hierarchy系统支持派生关系在两个名称之间,或类和名称之间。derive方法创建这些关系和isa!方法测试他们的关系。
您可以定义与 (派生子父级) 的hierarchy关系。子项和父项可以是符号或关键字,并且必须限定在命名空间
; 定义两个元素的关系
(derive ::rect ::shape)
(derive ::square ::rect)
; 返回::rect的父级
; #{:user/shape}
(parents ::rect)
; 返回::square的祖先集合
; #{:user/rect :user/shape}
(ancestors ::square)
; 返回::shape的子孙集合
; #{:user/rect :user/square}
(descendants ::shape)
; 测试两个元素的关系`
; true
(isa? ::square ::shape)
; true
(isa? String Object)
(derive ::rect ::shape)
; 消除歧义
(defmulti bar (fn [x y] [x y]))
(defmethod bar [::rect ::shape] [x y] :rect-shape)
(defmethod bar [::shape ::rect] [x y] :shape-rect)
(bar ::rect ::rect)
;-> Execution error (IllegalArgumentException) at user/eval152 (REPL:1).
; Multiple methods in multimethod 'bar' match dispatch value:
; [:user/rect :user/rect] -> [:user/shape :user/rect]
; and [:user/rect :user/shape], and neither is preferred
(prefer-method bar [::rect ::shape] [::shape ::rect])
(bar ::rect ::rect)
;-> :rect-shape
最后来一段clojure实现的面向对象的数据结构
这段代码需要注意的是:Shape也是一个函数,返回一个Map中的键为:Shape的值
(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
(* (:wd r) (:ht r)))
(defmethod area :Circle [c]
(* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
;-> 52
(area c)
;-> 452.3893421169302
(area {})
;-> :oops