前言

 在项目中我们一般会为实际问题域定义领域数据模型,譬如开发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/

尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/7154085.html _肥仔John

(cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")的更多相关文章

  1. iOS开发系列--Objective-C之协议、代码块、分类

    概述 ObjC的语法主要基于smalltalk进行设计的,除了提供常规的面向对象特性外,还增加了很多其他特性,这一节将重点介绍ObjC中一些常用的语法特性.当然这些内容虽然和其他高级语言命名不一样,但 ...

  2. iOS-Objective-C基础

    一.Foundation框架 概述 我们前面的章节中就一直新建Cocoa Class,那么Cocoa到底是什么,它和我们前面以及后面要讲的内容到底有什么关系呢?Objective-C开发中经常用到NS ...

  3. RichTextBoxEx2

    using System;using System.Collections.Specialized;using System.Drawing;using System.Drawing.Imaging; ...

  4. RichTextBoxEx

    using System; using System.Collections.Specialized; using System.Drawing; using System.Drawing.Imagi ...

  5. LR11

    HP LoadRunner Readme for the Windows operating system Software version: 11.00 Publication date: Octo ...

  6. 【Hadoop代码笔记】通过JobClient对Jobtracker的调用详细了解Hadoop RPC

    Hadoop的各个服务间,客户端和服务间的交互采用RPC方式.关于这种机制介绍的资源很多,也不难理解,这里不做背景介绍.只是尝试从Jobclient向JobTracker提交作业这个最简单的客户端服务 ...

  7. Qtp自动测试工具(案例学习)

    ♣Qtp是什么? ♣测试用例网站    ♦注册与登录    ♦测试脚本       ◊录制/执行测试脚本       ◊分析录制的测试脚本       ◊执行.查看测试脚本    ♦建立检查点     ...

  8. Windows批量添加防火墙例外端口

    Windows下批量添加防火墙例外端口,查了网上资料,基本上都是使用"Netsh命令",循环增加端口,这会导致建立的规则特别多,不便于管理,查了下微软的资料,原来是Netsh命令, ...

  9. jenkin插件整理

    分类 plugin名称 wiki地址 源码地址 plugin作用范围 备注 Build Reports构建报告(此类插件用来分析构建结果,比果代码检查,测试CASE分析,并将这些结果以报表,趋势图等形 ...

随机推荐

  1. Java 多线程详解(五)------线程的声明周期

    Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...

  2. 学习css3中的动画

    css animations 主要有两块构成,那么是哪两块呢? keyframes : 定义了什么阶段展示什么样的动画 animation 属性 :把动画挂载到一个具体的dom上,并且定义如何动起来: ...

  3. 6.类似Object监视器方法的Condition接口

    在<1.有关线程.并发的基本概念>中,我们利用synchronized关键字.Queue队列.以及Object监视器方法实现了生产者消费者,介绍了有关线程的一些基本概念.Object类提供 ...

  4. javascript基础-事件2

    DOM0,DOM2,DOM3事件类型 图解: 范畴 响应顺序(标:标准浏览器.IE9+) 注意点 MouseEvent 标: mousedown-mouseup-click-mousedown-mou ...

  5. 点评阿里JAVA手册之异常日志(异常处理 日志规约 )

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:异常处理 日志规约 本文难度系数为一星(★) 本文为第三篇 ...

  6. 一天搞定CSS:边框border--02

    每一个标签都是一个盒子,具体见HTML教程 因此,每一个标签有大小,有边框 1.border样式:单一样式 2.border样式:复合样式 border: 粗细 类型 颜色: 3.border方向 4 ...

  7. C#程序遍历数组A中所有元素

    ] { "a1","a2","a3","a4","a5"}; //第一种方法 ; i < A. ...

  8. .net、jquery、ajax、wcf实现数据库用户名检测局部刷新

    jquery代码 $(function() { $("#user_name").blur(function(){ var user_name=$("#user_name& ...

  9. java基础(十五章)

    一.字符串类String 1.String是一个类,位于java.lang包中 2.创建一个字符串对象的2种方式: String 变量名="值"; String 对象名=new S ...

  10. [codeforces631E]Product Sum

    E. Product Sum time limit per test: 1 second memory limit per test: 256 megabytes input:standard inp ...