(cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")
前言
在项目中我们一般会为实际问题域定义领域数据模型,譬如开发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) "一次说白DataType、Record和Protocol")的更多相关文章
- iOS开发系列--Objective-C之协议、代码块、分类
概述 ObjC的语法主要基于smalltalk进行设计的,除了提供常规的面向对象特性外,还增加了很多其他特性,这一节将重点介绍ObjC中一些常用的语法特性.当然这些内容虽然和其他高级语言命名不一样,但 ...
- iOS-Objective-C基础
一.Foundation框架 概述 我们前面的章节中就一直新建Cocoa Class,那么Cocoa到底是什么,它和我们前面以及后面要讲的内容到底有什么关系呢?Objective-C开发中经常用到NS ...
- RichTextBoxEx2
using System;using System.Collections.Specialized;using System.Drawing;using System.Drawing.Imaging; ...
- RichTextBoxEx
using System; using System.Collections.Specialized; using System.Drawing; using System.Drawing.Imagi ...
- LR11
HP LoadRunner Readme for the Windows operating system Software version: 11.00 Publication date: Octo ...
- 【Hadoop代码笔记】通过JobClient对Jobtracker的调用详细了解Hadoop RPC
Hadoop的各个服务间,客户端和服务间的交互采用RPC方式.关于这种机制介绍的资源很多,也不难理解,这里不做背景介绍.只是尝试从Jobclient向JobTracker提交作业这个最简单的客户端服务 ...
- Qtp自动测试工具(案例学习)
♣Qtp是什么? ♣测试用例网站 ♦注册与登录 ♦测试脚本 ◊录制/执行测试脚本 ◊分析录制的测试脚本 ◊执行.查看测试脚本 ♦建立检查点 ...
- Windows批量添加防火墙例外端口
Windows下批量添加防火墙例外端口,查了网上资料,基本上都是使用"Netsh命令",循环增加端口,这会导致建立的规则特别多,不便于管理,查了下微软的资料,原来是Netsh命令, ...
- jenkin插件整理
分类 plugin名称 wiki地址 源码地址 plugin作用范围 备注 Build Reports构建报告(此类插件用来分析构建结果,比果代码检查,测试CASE分析,并将这些结果以报表,趋势图等形 ...
随机推荐
- 【T-SQL性能优化】01.TempDB的使用和性能问题
以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 ...
- Include promo/activity effect into the prediction (extended ARIMA model with R)
I want to consider an approach of forecasting I really like and frequently use. It allows to include ...
- C#基础篇--面向对象(类与对象)
1.类是什么? 类就相当于模板,就是把同一类的事物的共同特征进行的抽象. 类的创建和说明: 类是先根据一些具体的对象(实体的东西)来抽象出来的共同的特性,然后用代码来表示. 在类中,用数据表示事物的 ...
- fidder从基础到熟练
一.fidder介绍 1.Fiddler是一款由C#语言开发的免费http调试代理软件,有.net 2 和 .net 4 两种版本.Fiddler能够记录所有的你电脑和互联网之间的http通讯,Fid ...
- css3中强大的filter(滤镜)属性
CSS3中强大的filter(滤镜)属性 博主最近在做网站的过程中发现了一个非常强大的CSS3属性,就是filter(滤镜)属性,喜欢p图的朋友看名字都应该知道这是什么神器了吧.当然,这个属性的效果肯 ...
- C语言到C++(1) - 基本变化
说到C++和C语言的区别,大部分人都会想到面向对象和面向过程.然而这种说法并不准确.面向对象和面向过程指的是两种不同的程序设计思想,而C++与C是两种编程语言,难道C++就不能用于面向过程去解决问题吗 ...
- React-Native集成到已有项目中的总结
安装Python 从官网下载并安装python 2.7.x(3.x版本不行) 安装node.js 从官网下载node.js的官方V6.X.X版本或更高版本.安装完成后检测是否安装成功:node -v ...
- 【基础】C#异常处理的总结
一.异常处理的理解? 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常. 因此处理这种错误,就称为异常处理. 二.异常处理如何操作? C# 异常处理时建立在四个关键词之上的: ...
- iOS enum C方法 DEBUG, RELEASE的隐藏的一个坑
开发了一个app, 在debug模式下没有任何问题,在release模式下就直接崩溃. 经过一段时间的定位终于定位到如下的这一段代码: E_BZ_TestType type = [dic[@" ...
- 最短路径Floyd算法【图文详解】
Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...