前言

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

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

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

示例

  1. (deftype VNode1 [tag props])
  2. (defrecord VNode2 [tag props])
  3. (def vnode1
  4. (VNode1. "DIV" {:textContent "Hello world!"}))
  5. ;; (->VNode1 "DIV" {:textContent "Hello world!"})
  6. (def vnode2
  7. (VNode2. "DIV" {:textContent "Hello world!"}))
  8. ;; (->VNode2 "DIV" {:textContent "Hello world!"})
  9. ;; (map->VNode2 {:tag "DIV", :props {:textContent "Hello world!"}})

 这样一看两者貌似没啥区别,其实区别在于成员的操作上

  1. ;; deftype取成员值
  2. (.-tag vnode1) ;;=> DIV
  3. ;; defrecord取成员值
  4. (:tag vnode2) ;;=> DIV
  5. ;; deftype修改成员值
  6. (set! (.-tag vnode1) "SPAN")
  7. ;; (aset vnode1 "tag" "SPAN"),这种方式不会改变vnode1的值
  8. (.-tag vnode1) ;;=> SPAN
  9. ;; defrecord无法修改值,只能产生一个新实例
  10. (def vnode3
  11. (assoc vnode2 :tag "SPAN"))
  12. (:tag vnode2) ;;=> DIV
  13. (:tag vnode3) ;;=> SPAN

 从上面我们可以看到defrecord定义的数据结构可以视作Map来操作,而deftype则不能。

 但上述均为术,而背后的道则是:

在OOP中我们会建立两类数据模型:1.编程领域模型;2.应用领域模型。对于编程领域模型(如String等),我们可以采用deftype来定义,从而提供特殊化能力;但对于应用领域模型而言,我们应该对其进行抽象,从而采用已有的工具(如assoc,filter等)对其进行加工,并且对于应用领域模型而言,一切属性应该均是可被访问的,并不存在私有的需要,因为一切属性均为不可变的哦。

Protocol

 Protocol如同Interface可以让我们实施面对接口编程。上面我们通过deftypedefrecord我们可以自定义数据结构,其实我们可以通过实现已有的Protocol或自定义的Protocol来扩展数据结构的能力。

deftypedefrecord在定义时实现Protocol

  1. ;; 定义protocol IA
  2. (defprotocol IA
  3. (println [this])
  4. (log [this msg]))
  5. ;; 定义protocol IB
  6. (defprotocol IB
  7. (print [this]
  8. [this msg]))
  9. ;; 定义数据结构VNode并实现IAIB
  10. (defrecord VNode [tag props]
  11. IA
  12. (println [this]
  13. (println (:tag this)))
  14. (log [this msg]
  15. (println msg ":" (:tag this)))
  16. IB
  17. (print ([this]
  18. (print (:tag this)))))
  19. ;; 各种调用
  20. (def vnode (VNode. "DIV" {:textContent "Hello!"}))
  21. (println vnode)
  22. (log vnode "Oh-yeah:")
  23. (print vnode)

注意IB中定义print为Multi-arity method,因此实现中即使是仅仅实现其中一个函数签名,也要以Multi-arity method的方式实现。

  1. (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

  1. ;; 扩展js/NodeList,让其可转换为seq
  2. (extend-type js/NodeList
  3. ISeqable
  4. (-seq [this]
  5. (let [l (.-length this)
  6. v (transient [])]
  7. (doseq [i (range l)]
  8. (->> i
  9. (aget this)
  10. (conj! v)))
  11. (persistent! v))))
  12. ;; 使用
  13. (map
  14. #(.-textContent %)
  15. (js/document.querySelector "div"))
  16. ;; 扩展js/RegExp,让其可直接作为函数使用
  17. (extend-type js/RegExp
  18. IFn
  19. (-invoke ([this s]
  20. (re-matches this s))))
  21. ;; 使用
  22. (#"s.*" "some") ;;=> some

2.使用extend-protocol

  1. ;; 扩展js/RegExpjs/String,让其可直接作为函数使用
  2. (extend-protocol IFn
  3. js/RegExp
  4. (-invoke ([this s] (re-matches this s)))
  5. js/String
  6. (-invoke ([this n] (clojure.string/join (take n this)))))
  7. ;; 使用
  8. (#"s.*" "some") ;;=> some
  9. ("test" 2) ;;=> "te"

 另外我们可以通过satisfies?来检查某数据类型实例是否实现指定的Protocol

  1. (satisfies? IFn #"test") ;;=> true
  2. ;;对于IFn我们可以直接调用Ifn?
  3. (Ifn? #"test") ;;=>true

reify构造实现指定Protocol的无属性实例

  1. (defn user
  2. [firstname lastname]
  3. (reify
  4. IUser
  5. (full-name [_] (str firstname lastname))))
  6. ;; 使用
  7. (def me (user "john" "Huang"))
  8. (full-name me) ;;=> johnHuang

specifyspecify!为实例追加Protocol实现

specify可为不可变(immutable)和可复制(copyable,实现了ICloneable)的值,追加指定的Protocol实现。其实就是向cljs的值追加啦!

  1. (def a "johnHuang")
  2. (def b (specify a
  3. IUser
  4. (full-name [_] "Full Name")))
  5. (full-name a) ;;=>报错
  6. (full-name b) ;;=>Full Name

specify!可为JS值追加指定的Protocol实现

  1. (def a #js {})
  2. (specify! a
  3. IUser
  4. (full-name [_] "Full Name"))
  5. (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. 【T-SQL性能优化】01.TempDB的使用和性能问题

    以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 ...

  2. 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 ...

  3. C#基础篇--面向对象(类与对象)

    1.类是什么?  类就相当于模板,就是把同一类的事物的共同特征进行的抽象. 类的创建和说明: 类是先根据一些具体的对象(实体的东西)来抽象出来的共同的特性,然后用代码来表示. 在类中,用数据表示事物的 ...

  4. fidder从基础到熟练

    一.fidder介绍 1.Fiddler是一款由C#语言开发的免费http调试代理软件,有.net 2 和 .net 4 两种版本.Fiddler能够记录所有的你电脑和互联网之间的http通讯,Fid ...

  5. css3中强大的filter(滤镜)属性

    CSS3中强大的filter(滤镜)属性 博主最近在做网站的过程中发现了一个非常强大的CSS3属性,就是filter(滤镜)属性,喜欢p图的朋友看名字都应该知道这是什么神器了吧.当然,这个属性的效果肯 ...

  6. C语言到C++(1) - 基本变化

    说到C++和C语言的区别,大部分人都会想到面向对象和面向过程.然而这种说法并不准确.面向对象和面向过程指的是两种不同的程序设计思想,而C++与C是两种编程语言,难道C++就不能用于面向过程去解决问题吗 ...

  7. React-Native集成到已有项目中的总结

    安装Python 从官网下载并安装python 2.7.x(3.x版本不行) 安装node.js 从官网下载node.js的官方V6.X.X版本或更高版本.安装完成后检测是否安装成功:node -v ...

  8. 【基础】C#异常处理的总结

    一.异常处理的理解? 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常. 因此处理这种错误,就称为异常处理. 二.异常处理如何操作? C# 异常处理时建立在四个关键词之上的: ...

  9. iOS enum C方法 DEBUG, RELEASE的隐藏的一个坑

    开发了一个app, 在debug模式下没有任何问题,在release模式下就直接崩溃. 经过一段时间的定位终于定位到如下的这一段代码: E_BZ_TestType type = [dic[@" ...

  10. 最短路径Floyd算法【图文详解】

    Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...