(cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")
2017-07-12 10:22
573 查看
前言
在项目中我们一般会为实际问题域定义领域数据模型,譬如开发VDOM时自然而言就会定义个VNode数据类型,用于打包存储、操作相关数据。clj/cljs不单内置了List、
Vector、
Set和
Map等数据结构,还提供
deftype和
defrecord让我们可以自定义数据结构,以满足实际开发需求。
定义数据结构从Data Type和Record开始
提及数据结构很自然就想起C语言中的struct,结构中只有字段并没有定义任何方法,而这也是deftype和
defrecord最基础的玩法。
示例
(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!"}})
这样一看两者貌似没啥区别,其实区别在于成员的操作上
;; 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可以让我们实施面对接口编程。上面我们通过deftype和
defrecord我们可以自定义数据结构,其实我们可以通过实现已有的Protocol或自定义的Protocol来扩展数据结构的能力。
deftype
和defrecord
在定义时实现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-type
;; 扩展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-protocol
;; 扩展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?来检查某数据类型实例是否实现指定的Protocol
(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
specify
和specify!
为实例追加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,
defrecord和
defprotocol让我们从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
相关文章推荐
- (cljs/run-at (JSVM. :all) "细说函数")
- (cljs/run-at (JSVM. :all) "一起实现柯里化")
- (cljs/run-at (JSVM. :all) "Metadata就这样哦")
- (cljs/run-at (->JSVM :browser) "语言基础")
- (cljs/run-at (JSVM. :browser) "搭建刚好可用的开发环境!")
- (cljs/run-at (JSVM. :browser) "命名空间就这么简单")
- (cljs/run-at (JSVM. :browser) "简单类型可不简单啊~")
- //另一种提交type='file'的方法,不需要用到插件query-form.js var formData = new FormData($( "#formFile" )[0]); $.ajax
- 奇怪的母版页里面的 form 表单里面的 enctype="multipart/form-data" html控件上传 FileUpload控件上传 一次多图片上传
- enctype="multipart/form-data"
- 表单form的enctype="multipart/form-data"使用体会
- JS "portotype" (内置对象原型改写)
- php在form表单ENCTYPE="multipart/form-data" 不用转换编码
- Solution: 无法察看SQL Server 数据库属性 错误: "There is no row at position 0. (System.Data)"
- 解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的方法 ?
- 解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的方法
- asp中enctype="multipart/form-data"获取Form表单的值
- 使用JS清空上传控件input(type="file")的值
- mysql Data too long for column " 列名" at row 1
- struts中enctype="multipart/form-data",request.getParameter("file") 是null的问题分