Clojure雖然是一門函數(shù)式編程語言,當也能很容易支持類似OOP那種polymorphism灵再,能讓我們寫出更好的抽象代碼。
Multimethods
使用multimethod是一種快速在代碼里面引入polymorphism的方法汪榔,我們可以定義一個dispatching function雌团,然后指定一個dispatching value,通過它來確定調用哪一個函數(shù)灵寺。
譬如替久,我們需要計算一個圖形的面積,我們知道颅拦,如果是一個長方形,那么方法就是with * heigth
碌秸,如果是圓形,那么就是 PI * radius * radius
。
; define a multimethod for area with :Shape keyword.
(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))
我們在repl里面執(zhí)行:
user=> (area r)
52
user=> (area c)
452.3893421169302
user=> (area {})
:oops
Protocol
從上面multimethod的實現(xiàn)可以,multimethod只是一個polymorphic操作恢口,如果我們想實現(xiàn)多個,那么multimethod就不能滿足了沙峻,這時候,我們就可以使用protocol是复。
protocol其實更類似其他語言里面interface,我們定義一個protocol,然后用不同的類型去特化實現(xiàn)画拾,我們以jepsen的代碼為例,因為jepsen可以測試很多db蜜另,所以它定義了一個db的protocol鹅很。
(defprotocol DB
(setup! [db test node] "Set up the database on this particular node.")
(teardown! [db test node] "Tear down the database on this particular node."))
上面的代碼定義了一個DB的protocol菠齿,然后有兩個函數(shù)接口,用來setup和teardown對應的db戈钢,所以我們只需要在自己的db上面實現(xiàn)這兩個函數(shù)就能讓jepsen調用了拟枚,偽代碼如下:
(def my-db
(reify DB
(setup! [db test node] "hello db")
(teardown! [db test node] "goodbye db")))
user=> (setup! my-db :test :node)
"hello db"
user=> (teardown! my-db :test :node)
"goodbye db"
Record
有些時候,我們還想在clojure中實現(xiàn)OOP語言中class的效果,用record就能很方便的實現(xiàn)帮掉。record類似于map涩搓,這點就有點類似于C++ class中的field,然后還能實現(xiàn)特定的protocol,這就類似于C++ class的member function了。
record的定義很簡單肘习,我們使用defrecord來定義:
user=> (defrecord person [name age])
user.person
這里,我們定義了一個person的record,它含有name和age兩個字段,然后我們可以通過下面的方法來具體創(chuàng)建一個person:
; 使用類似java的 . 操作符創(chuàng)建
user=> (person. "siddon" 30)
#user.person{:name "siddon", :age 30}
; 通過 ->person 函數(shù)創(chuàng)建
user=> (->person "siddon" 30)
#user.person{:name "siddon", :age 30}
; 通過 map->persion 函數(shù)創(chuàng)建,參數(shù)是map
user=> (map->person {:name "siddontang" :age 30)
因為record其實可以認為是一個map,所以很多map的操作靶擦,我們也同樣可以用于record上面枚粘。
user=> (def siddon (->person "siddon" 30))
#'user/siddon
user=> (assoc siddon :name "tang")
#user.person{:name "tang", :age 30}
user=> (dissoc siddon :name)
{:age 30}
record可以實現(xiàn)特定的protocol局骤,譬如:
(defprotocol SayP
(say [this]))
(defrecord person [name age]
SayP
(say [this] (str "hello " name)))
上面我們定義了SayP這個protocol,并且讓person這個record實現(xiàn)了相關的函數(shù)犬辰,然后我們就可以直接使用了。
user=> (say (->person "siddon" 30))
"hello siddon"