博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")
阅读量:7126 次
发布时间:2019-06-28

本文共 3903 字,大约阅读时间需要 13 分钟。

前言

 在项目中我们一般会为实际问题域定义领域数据模型,譬如开发VDOM时自然而言就会定义个VNode数据类型,用于打包存储、操作相关数据。clj/cljs不单内置了ListVectorSetMap等数据结构,还提供deftypedefrecord让我们可以自定义数据结构,以满足实际开发需求。

定义数据结构从Data Type和Record开始

 提及数据结构很自然就想起C语言中的struct,结构中只有字段并没有定义任何方法,而这也是deftypedefrecord最基础的玩法。

示例

(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可以让我们实施面对接口编程。上面我们通过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-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

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/

尊重原创,转载请注明来自: ^_^肥仔John

如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!

 

本文转自博客园博客,原文链接:,如需转载请自行联系原作者

 

你可能感兴趣的文章
tensorflow入门简单卷积神经网络
查看>>
【我的软考之路】我的网工备考之路~
查看>>
Google决定用gLinux取代Goobuntu Linux操作系统
查看>>
《将博客搬至CSDN》
查看>>
负载产品性能测试——新建测试
查看>>
mongo学习记录
查看>>
ReactOS:基于Windows的开源操作系统
查看>>
在 Linux 中调试 C 程序的福音——gdb
查看>>
这些年一起学过的Linux
查看>>
QQ邮箱无法收到系统邮件的问题处理
查看>>
gogoprotobuf使用(上)
查看>>
HBase–调优篇
查看>>
word的多级列表&自动编号
查看>>
SSH之密钥登陆
查看>>
批量上传公钥到Linux服务器
查看>>
关于日立存储更换故障硬盘
查看>>
从程序员到技术领导者
查看>>
squid的配置及应用
查看>>
pycharm,vim,items2常用快捷键
查看>>
数据支撑环境的改造
查看>>