【读书】JavaScript 设计模式与开发实践
2016.08.30
《JavaScript 设计模式与开发实践》 曾探 人民邮电出版社 2016年5月第1版
p13
找到变化的部分并封装之,以使得容易替换;而剩下的就是不变的部分。
P49
函数柯里化(currying)的作用是多次收集参数,然后作为数组传给处理函数再一次执行。
其意义在于预处理——将预处理的流程放到一个函数里会更为清晰可控。
P57
惰性加载函数
在函数内部重写引用函数的外部变量的引用,从而在第一次”调用”此变量后,此变量就指向新的正确的函数。
p84
这里的errorMsg不应该包含在strategies的项里。这些项只要返回true/false就好,msg关他们什么事啊!
也许是msg要根据strategie而变化吧。
P86
策略模式的目的是将算法独立出代码的执行部分(content),使计算的代码可复用、实现灵活。
关键点在于看出何为策略的最低依赖描述、考虑如何从content进入算法中。
P89
关于代理模式,所谓代理是在访问者-被访问者这两者间的代理者。被访问者提供了一些接口供任意人访问。这些接口在某些情况下不应被调用或应以另一个形式被调用,这时为了保证接口内的职责单一性,应该隔一层调用这个接口;这一层用于处理那些特定情况,决定如何调用接口,这层就是代理。
代理模式的目的是为了保持接口的职责单一性。
代理模式的主要特点是代理者与被代理者(即被访问者)拥有职责一致的接口,一般接口名称也一致。
由于需要处理的情形很多,代理的模式也各种各样:
保护代理: 用于接口有不同访问权限的情况
虚拟代理: 目的是延迟对接口的访问,到有必要时再访问
缓存代理: 存储接口结果,参数一致时不调用接口直接返回已存结果。
等等。也不是说使用了虚拟代理就不能使用保护代理,以上列表只是说明这些情景下需要代理。
P93
单一职责原则指的是:就一个类(也包括对象与函数)而言,应该仅有一个引起它变化的原因。如果一个类承担了多项职责,就意味着这个类将变得巨大,引起它变化的原因可能会有多个。
职责被定义为”引起变化的原因”。当一个类承担多个职责时,由于其高耦合性,一个职责的处理可能会影响到另一个职责的处理。
代理层能承担部分职责,使接口职责单一。
p124
发布-订阅模式的推模型的问题在于:订阅者收到的数据是由发布者决定的。
而拉模型的问题在于发布者可能会变成一个公开的对象。
首先,选用拉模型。
1. 发布时传一个对象,此对象上拥有一系列接口供订阅者获取数据。可能需要用代理模式。
2. 在订阅时传入一些函数,这些函数将在结果上调用并返回调用结果(太复杂)。
发布-订阅模式里也用到了策略模式。
P131
命令模式的意义在于将职责分到三部分中:命令者-命令-执行者。另一个更重要的意义是命令将变成可存储、调用的数据。
p132
疑点在于这里的演示,命令moveCommand与执行者animate是绑定的,也就是说moveCommand不能用于命令animate2。如果有多个执行者,必须生成多个对应的命令。
其实可以实现成moveCommand.execute(animate2)。
不过仔细想想假如执行者不止animate2还有xxx2的话?多执行者这种情况可以有的吧?
这样子可能就是实现成:
var MoveCommand = function(receiver, pos){
......
this.receivers = [receiver]
}
MoveCommand.prototype.execute = function(){
if(!arguments.length){
this._execute.apply(this, this.receivers)
} else {
this._execute.apply(this, arguments)
}
}
MoveCommand.prototype._execute = function(receiver){
receiver.start......
}
命令里包含执行者也有好处,存储命令就能存储执行者了。
p132
组合模式的关键是接口一致。
p151
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架。,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
p160
钩子方法为什么一定要是方法呢?
因为可以动态得到结果,只是要怎样让外部能改变到钩子是个问题。
- 通过this访问。也就是这里的实现。
2. 作为闭包暴露钩子。
p173
假如有2000个文件,uploadDatabase里根本就会存2000个对象。只不过不是2000个upload对象。
所以我认为享元模式的意义在于将数据与模板分离,从而共享了一个结构、一些方法。
需要使用一个对象时,拿数据填模板,就得到指定结构的对象与可用的方法。
而这里所谓的”有多少个内部状态,就有多少个共享对象”,内部状态指的是模板,共享对象指的是模板的相应数据化结果(具体到这里的代码就是flyWeightObj,而不是Upload构造器。Upload构造器说到底跟享元模式无关,只是”元”的抽象类,是内部状态的抽象。12.6.1有例证)。
由于将数据分离,所以在本书的实现中,数据需要一个UploadManager来管理,并为模板里的方法提供一个UploadManager.setExternalState以读取数据填充到指定对象(就是模板本身)。
关于这个实现,我觉得这里flyWeightObj.delFile(id)和uploadManager.setExternalState(id, this)同时出现就不好,相互调用感觉就不好。(应该没有相互引用)
是不是改成flyWeightObj.delFile(id); this.setExternalState(id, flyWeightObj); 会比较好?
貌似他这么做也无不妥,在flyWeightObj.delFile里先将数据填充到本身上再删除数据,是一种合理的做法。毕竟delFile可能会被多次调用,总不能每次调用前都写一句setExternalState吧?
如果不需要存在”元”,那就没有必要使用享元模式。
p185
职责链模式的最大优点就是解耦了请求发送者和N个接受者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。
本书实现的职责链的问题在于程序员不知道链的具体模样。所以A-B-D,我要往B后面插个C就必须知道B与D的引用,并修改它们的nextNode属性。
可以var chainCtrl = new ChainCtrl(); var A = chainCtrl.cteateAndAppendNode(fnA), B = ......; var C = chainCtrl.createNode(fnC); chainCtrl.insertAfter(C, B);
以上的具体实现里,不仅要维护一个存在ChainCtrl内部的数组,仍需要实现node.nextNode属性,这样才能够调用B.next()以跳过A从B开始执行职责链。
p187
这里提到AOP(面向切面编程)。
AOP解决的问题是希望在函数执行前或执行后插入代码又不希望改动原函数。(如果希望在函数执行中某个位置插入代码,恐怕只能使用钩子)
AOP的关键是用一个函数调用原函数并代替原函数被使用。
顺便一说,LISP里天然实现AOP。
p189
中介者模式解决代码块间联系太多,耦合强的问题。(就像在心脏旁边拆掉一根毛细血管一般,即使一点很小的修改也必须小心翼翼)
p195
这里在构造player时传入teamColor我认为是不对的。”team”是一个外部状态而不是player所固有的东西,应该隔一层在playerFactory这个函数里调用setTeam(player)为player设置属性。要不然以后需求改成”当一个队伍满2人时,新的玩家将进入不满员队伍或新建队伍”,这时候势必要:
- 在Player构造器加判断逻辑。这当然不行。
- Player构造器不直接加判断逻辑而是使用钩子。我认为这也半斤八两,毕竟要调用与team相关的外部接口。
- 使用AOP,替换掉Player。可替换掉构造器貌似不妥。
还是移除在Player里对teamColor属性的设置比较好。这时可以:
1.在playerFactory使用钩子。
2.在playerFactory这个函数里调用setTeam(player)
p195
此处Player.prototype.die 等方法里调用了playerDirector.ReceiveMessage,这样就依赖了中介者模式的playerDirector,日后如果想换个模式势必要改动这里的代码。建议隔一层。
要这么说,所有调用外部接口的都应隔一层了。
p198
这里关于掉线的处理有问题,假如先die再remove,因为remove里没有检查,另一方就不能获得胜利了。
可以考虑使用AOP
p199
说到购买商品的例子,能不能创造一种”流程模式”?将流程放到数组里,调用next()则进入下一个流程,调用jump(fn)则跳到fn对应的流程。
还有就是想说,这里可以使用mvc的理念。在页面上操作,然后进入中介者,然后中介者处理一个数据结构,然后中介者调用一个渲染函数根据这个数据结构对页面进行渲染。
问题在于如何只渲染需要渲染的内容。
p206
这里的changed太过臃肿,耦合度也高,应该把if-else分割成函数放到一个obj里,然后在changed里遍历obj的成员并用职责链模式将它们连接起来。
而剩余部分,或许也可以分割成函数并使用AOP连接。
p208
过度抽象会使得无形状,不直观。
p211
装饰者模式的意图是减少子类与实例的数量。
同名调用同名听起来有点像代理模式。
就逻辑业务上两者的区别,代理者的业务与被代理者的业务是相关的,装饰者与被装饰者的业务可以是毫无关联的。
就实现上而言,代理者是拥有被代理者同名方法的一个对象(或函数);装饰者是在类A的原型方法中调用另一个类B创建的实例的方法,最后调用new A()得到一个实例,或者使用AOP替换原方法。
如果被装饰者是单例,装饰者模式是会改变被装饰者的,代理模式则可以不改变它。