(cljs/run-at (JSVM. :all) "一次說白DataType、Record和Protocol")

前言

 在項目中咱們通常會爲實際問題域定義領域數據模型,譬如開發VDOM時天然而言就會定義個VNode數據類型,用於打包存儲、操做相關數據。clj/cljs不單內置了ListVectorSetMap等數據結構,還提供deftypedefrecord讓咱們能夠自定義數據結構,以知足實際開發需求。html

定義數據結構從Data Type和Record開始

 說起數據結構很天然就想起C語言中的struct,結構中只有字段並無定義任何方法,而這也是deftypedefrecord最基礎的玩法。
示例java

(deftype VNode1 [tag props])
(defrecord VNode2 [tag props])

(def vnode1
  (VNode1. "DIV" {:textContent "Hello world!"}))
;; 或 (->VNode1 "DIV" {:textContent "Hello world!"})

(def vnode2
  (VNode2. "DIV" {:textContent "Hello world!"}))
;; 或 (->VNode2 "DIV" {:textContent "Hello world!"})
;; 或 (map->VNode2 {:tag "DIV", :props {:textContent "Hello world!"}})

 這樣一看二者貌似沒啥區別,其實區別在於成員的操做上node

;; deftype取成員值
(.-tag vnode1) ;;=> DIV
;; defrecord取成員值
(:tag vnode2)  ;;=> DIV

;; deftype修改爲員值
(set! (.-tag vnode1) "SPAN")
;; 而 (aset vnode1 "tag" "SPAN"),這種方式不會改變vnode1的值
(.-tag vnode1) ;;=> SPAN

;; defrecord沒法修改值,只能產生一個新實例
(def vnode3
  (assoc vnode2 :tag "SPAN"))
(:tag vnode2) ;;=> DIV
(:tag vnode3) ;;=> SPAN

 從上面咱們能夠看到defrecord定義的數據結構能夠視做Map來操做,而deftype則不能。
 但上述均爲術,而背後的道則是:
在OOP中咱們會創建兩類數據模型:1.編程領域模型;2.應用領域模型。對於編程領域模型(如String等),咱們能夠採用deftype來定義,從而提供特殊化能力;但對於應用領域模型而言,咱們應該對其進行抽象,從而採用已有的工具(如assoc,filter等)對其進行加工,而且對於應用領域模型而言,一切屬性應該均是可被訪問的,並不存在私有的須要,由於一切屬性均爲不可變的哦。編程

Protocol

 Protocol如同Interface可讓咱們實施面對接口編程。上面咱們經過deftypedefrecord咱們能夠自定義數據結構,其實咱們能夠經過實現已有的Protocol或自定義的Protocol來擴展數據結構的能力。數據結構

deftypedefrecord在定義時實現Protocol

;; 定義protocol IA
(defprotocol IA
  (println [this])
  (log [this msg]))

;; 定義protocol IB
(defprotocol IB
  (print [this]
         [this msg]))

;; 定義數據結構VNode並實現IA和IB
(defrecord VNode [tag props]
  IA
  (println [this]
    (println (:tag this)))
  (log [this msg]
    (println msg ":" (:tag this)))
  IB
  (print ([this]
    (print (:tag this)))))

;; 各類調用
(def vnode (VNode. "DIV" {:textContent "Hello!"}))
(println vnode)
(log vnode "Oh-yeah:")
(print vnode)

注意IB中定義print爲Multi-arity method,所以實現中即便是僅僅實現其中一個函數簽名,也要以Multi-arity method的方式實現。函數

(print ([this] (print (:tag this))))

不然會報java.lang.UnsupportedOperationException: nth not supported on this type: Symbol的異常工具

對已有的數據結構追加實現Protocol

 Protocol強大之處就是咱們能夠在運行時擴展已有數據結構的行爲,其中可經過extend-type對某個數據結構實現多個Protocol,經過extend-protocol對多個數據結構實現指定Protocol。
1.使用extend-typethis

;; 擴展js/NodeList,讓其可轉換爲seq
(extend-type js/NodeList
  ISeqable
  (-seq [this]
    (let [l (.-length this)
          v (transient [])]
      (doseq [i (range l)]
        (->> i
          (aget this)
          (conj! v)))
      (persistent! v))))
;; 使用
(map
  #(.-textContent %)
  (js/document.querySelector "div"))

;; 擴展js/RegExp,讓其可直接做爲函數使用
(extend-type js/RegExp
  IFn
  (-invoke ([this s]
    (re-matches this s))))

;; 使用
(#"s.*" "some") ;;=> some

2.使用extend-protocolcode

;; 擴展js/RegExp和js/String,讓其可直接做爲函數使用
(extend-protocol IFn
  js/RegExp
  (-invoke ([this s] (re-matches this s)))
  js/String
  (-invoke ([this n] (clojure.string/join (take n this)))))

;; 使用
(#"s.*" "some") ;;=> some
("test" 2) ;;=> "te"

 另外咱們能夠經過satisfies?來檢查某數據類型實例是否實現指定的Protocolhtm

(satisfies? IFn #"test") ;;=> true
;;對於IFn咱們能夠直接調用Ifn?
(Ifn? #"test") ;;=>true

reify構造實現指定Protocol的無屬性實例

(defn user
  [firstname lastname]
  (reify
    IUser
    (full-name [_] (str firstname lastname))))
;; 使用
(def me (user "john" "Huang"))
(full-name me) ;;=> johnHuang

specifyspecify!爲實例追加Protocol實現

specify可爲不可變(immutable)和可複製(copyable,實現了ICloneable)的值,追加指定的Protocol實現。其實就是向cljs的值追加啦!

(def a "johnHuang")
(def b (specify a
         IUser
         (full-name [_] "Full Name")))

(full-name a) ;;=>報錯
(full-name b) ;;=>Full Name

specify!可爲JS值追加指定的Protocol實現

(def a #js {})
(specify! a
  IUser
  (full-name [_] "Full Name"))

(full-name a) ;;=> "Full Name"

總結

 cljs建議對數據結構進行抽象,所以除了List,Map,Set,Vector外還提供了Seq;並內置一系列數據操做的函數,如map,filter,reduce等。而deftype、defrecord更可能是針對面向對象編程來使用,或者是面對內置操做不足以描述邏輯時做爲擴展的手段。也正是deftype,defrecorddefprotocol讓咱們從OOP轉FP時感受更加舒坦一點。
 另外deftype,defrecord和protocol這套還有效地解決Expression Problem,具體請查看http://www.ibm.com/developerworks/library/j-clojure-protocols/

尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/7154085.html ^_^肥仔John

相關文章
相關標籤/搜索