职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

书里的订单的例子

假设我们负责一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预定(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠券,200元定金的用户可以收到50元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

orderType:表示订单类型(定金用户或者普通购买用户),值为1的时候是500元定金用户,为2的时候是200元定金用户,为3的时候是普通购买用户。
pay:表示用户是否已经支付定金,值为true表示已付钱,如果没有支付定金,只能降级进入普通购买模式。
num:表示当前用于普通购买的手机库存数量,已经支付过500元或者200元定金的用户不受此限制。

     var order=function( orderType, pay, num ){
if ( orderType === 1 ){ // 500元定金购买模式
if ( pay === true ){ // 已支付定金
console.log( '500元定金预购, 得到100优惠券' );
}else{ // 未支付定金,降级到普通购买模式
if ( num > 0 ){ // 用于普通购买的手机还有库存
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 2 ){ // 200元定金购买模式
if ( pay === true ){
console.log( '200元定金预购, 得到50优惠券' );
}else{
if ( num > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 3 ){ //普通购买
if ( num > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
} order( 1 , true, 500); // 500元定金预购, 得到100优惠券

上面的代码其实不用看完就已经能感受到十分的繁琐,把所有的逻辑都列出来使得函数变得十分臃肿。这种类似的代码在策略模式中其实出现过,最后使用策略模式很好的消化了内部的条件分支,这里的话我们用职责链模式也能解决。

使用职责链的思想重写函数

思路:彼此委托,我可以执行我就执行,我执行不了我就委托给别人执行,所以我们把500订单的执行,200订单的执行和普通购买的执行分别写成一个函数。

     var order500=function( orderType, pay, num ){
if ( orderType === 1 && pay === true ){ //如果是500订单且已经付款
console.log( '500元定金预购, 得到100优惠券' );
}else{
order200( orderType, pay, num ); //将请求传递给200 元订单
}
}; var order200=function( orderType, pay, num ){
if ( orderType === 2 && pay === true ){ //如果是200订单且已经付款
console.log( '200元定金预购, 得到50优惠券' );
}else{
orderNormal( orderType, pay, num ); //将请求传递给普通订单
}
}; var orderNormal=function( orderType, pay, num ){
if ( num > 0 ){ //普通购买,如果数量足够
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
} order500( 1 , true, 500 ); //500元定金预购, 得到100优惠券
order500( 1, false, 500 ); //普通购买, 无优惠券
order500( 2 , true, 500 ); //200元定金预购, 得到50优惠券

这个函数比之前的已经好了很多,但是还是有些缺陷,假设说我们增添了300元的订单,那么我们就要改写上面的函数。

可以说,只要有点改动,无论是增加还是删除哪一环,我们必须拆开这个链条才行,而且必须修改函数内部。

那这个问题的根源在哪里,传递请求是职责链模式的根本,也是这个函数和上一个函数最大的区别所在,所以传递请求这一点是没有问题的,我们需要他们一个委托一个,那么问题的所在就是委托函数之间耦合的太死,以至于改动必须深入内部才行。所以,我们要想办法降低委托函数彼此间的耦合。

进一步修改函数

思路:想要解耦合,那么就是用变量代替具体的函数,然后用一个对象安排每个函数之后的委托函数。但是每个函数后面的委托函数是不一样的,所以不能使用相同的变量,但是如果使用不同的变量,其实质和现在并没有多少区别。

从委托这个字眼,我们想到对象之间可以很方便的调用方法,对象接收这个函数,并产生相应的属性,包括当前的函数,和之后需要执行的委托函数。仔细一想,我们的做法其实相当于扩展原来的函数,在不改动原来函数的基础上,我们想要实现委托,那就只有把函数变成参数来生成一个更为全能的对象,然后让这几个功能更强的对象之间彼此交互。因为这些对象彼此类似,功能也相同,所以我们需要一个模板来生成这些对象。同时为了让对象可以意识到该委托别的对象了,函数的返回值应该做一个统一规定。

     /**首先是具体的功能函数**/
var order500=function( orderType, pay, num ){
if ( orderType === 1 && pay === true ){ //如果是500订单且已经付款
console.log( '500元定金预购, 得到100优惠券' );
}else{
return "next" //统一规定的执行结果
}
}; var order200=function( orderType, pay, num ){
if ( orderType === 2 && pay === true ){ //如果是200订单且已经付款
console.log( '200元定金预购, 得到50优惠券' );
}else{
return "next"
}
}; var orderNormal=function( orderType, pay, num ){
if ( num > 0 ){ //普通购买,如果数量足够
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
} /**闭包创建一个类,实例职责对象,参数就是上面的功能函数**/
var order=(function(){
var constructor=function( fn ){ //构造器,存储当前的函数和后面委托的对象
this.fn=fn;
this.next=null;
}
constructor.prototype.setnext=function( nextobj ){ //设定委托的对象
return this.next=nextobj;
}
constructor.prototype.do=function(){ //执行函数
var result=this.fn.apply(this,arguments); //获得执行结果 if( result==="next" ){ //当执行结果为规定值时
return this.next.do.apply(this.next,arguments); //委托对象执行函数
}
return result;
} return constructor;
})(); /**实例过程**/
var order_500=new order( order500 );
var order_200=new order( order200 );
var order_normal=new order( orderNormal ); /**职责对象间设定委托的链条关系**/
order_500.setnext( order_200 );
order_200.setnext( order_normal ); /**初始调用的接口**/
/**这里其实可以再包装一个固定的接口,这样不管链条究竟从哪里开始,我们也不需要改变初始的调用函数**/
order_500.do( 2, true, 500 ); //200元定金预购, 得到50优惠券

这时候如果加了300元的订单也很方便

     var order_300=new ( order300 );
order_500.setnext( order_300 );
order_300.setnext( order_200 );

小结:我们把手动的修改,变成了通过对象控制,这样只需要几个句子就能很方便的修改业务逻辑,不用去改动任何源码。

用es6的类来完成

     class order{
constructor( fn ){
this.fn=fn;
this.next=null;
}
setnext( nextobj ){
return this.next=nextobj;
}
do(){
var result=this.fn.apply(this,arguments); //获得执行结果 if( result==="next" ){ //当执行结果为规定值时
return this.next.do.apply(this.next,arguments); //委托对象执行函数
}
return result;
}
}

异步职责链

上面的代码我们是约定好了一个返回值,并把这个返回值写死在了原型方法上,这样我们可以直接得到最终答案,但实际上很多时候,可能我们函数的执行需要等待一个ajax的响应,这种时候时间上是不同步的,而且返回的执行结果我们也不能保证一致,所以我们可以添加一个方法,用来手动触发下一个委托函数。

     constructor.prototype.nextfun= function(){
return this.next && this.next.do.apply( this.next, arguments ); //如果next存在 就执行它的方法
}; var fn1 = new order(function(){
console.log( 1 );
return 'next';
});
var fn2 = new order(function(){
console.log( 2 );
var self = this;
setTimeout(function(){
self.nextfun();
}, 1000 );
}); var fn3 = new order(function(){
console.log( 3 );
});
fn1.setnext( fn2 ).setnext( fn3 ); //链式调用,因为setnext方法返回了对象
fn1.do(); //先出现1,2 隔了一秒之后出现3

职责链模式的优缺点

优点如我们所见,本来有很多等待执行的函数,我们不知道哪一个可以执行请求的时候就要一个一个去验证,于是出现了最开始的那个代码。而在职责链模式中,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。

使用了职责链模式之后,链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情。这一点我们也已经看到,在上面的例子中,增加一种订单完全不需要改动其他订单函数中的代码。

而且职责链模式还有一个优点,那就是可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递,当我们明确第一个节点并不具有执行能力时,我们可以从第二个或者更后面的节点开始传递,这样可以减少请求在链里面传递的次数。

缺点,可能没有一个节点可以执行,请求会从链尾离开或者抛出一个错误。而且为了职责链而职责链可能因为过多没有必要的节点,带来性能方面的问题。

总结

职责链模式在js开发很容易被忽略,它结合无论是结合组合模式还是利用AOP的思想,都能发挥巨大作用。

《javascript设计模式与开发实践》阅读笔记(13)—— 职责链模式的更多相关文章

  1. javascript设计模式与开发实践阅读笔记(8)——观察者模式

    发布-订阅模式,也叫观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 在JavaScript开发中,我们一般用事件模型来替代传统的观察者模式. ...

  2. javascript设计模式与开发实践阅读笔记(4)——单例模式

    定义 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 具体来说,就是保证有些对象有且只有一个,比如线程池.全局缓存.浏览器中的window 对象等.在js中单例模式用途很广,比如登录 ...

  3. javascript设计模式与开发实践阅读笔记(7)——迭代器模式

    迭代器模式:指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺 ...

  4. javascript设计模式与开发实践阅读笔记(6)——代理模式

    代理模式:是为一个对象提供一个代用品或占位符,以便控制对它的访问. 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对 ...

  5. javascript设计模式与开发实践阅读笔记(5)——策略模式

    策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 我的理解就是把各种方法封装成函数,同时存在一个可以调用这些方法的公共函数.这样做的好处是可以消化掉内部的分支判断,使代码效率 ...

  6. javascript设计模式与开发实践阅读笔记(9)——命令模式

    命令模式:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 说法很复 ...

  7. javascript设计模式与开发实践阅读笔记(11)—— 模板方法模式

    模板方法模式: 由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类.通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序.子类通过继承这个抽象类,也继 ...

  8. JavaScript设计模式与开发实践——读书笔记1.高阶函数(上)

    说来惭愧,4个多月未更新了.4月份以后就开始忙起来了,论文.毕设.毕业旅行等七七八八的事情占据了很多时间,毕业之后开始忙碌的工作,这期间一直想写博客,但是一直没能静下心写.这段时间在看<Java ...

  9. 《javascript设计模式与开发实践》--- (单一职责原则)

    看的这本书叫<JavaScript设计模式与开发实践> 先规划一下看书的顺序,基础知识我已经大概的浏览了一遍了,没有留下笔记,以后有时间还会补上.本来打算顺着看的.但是我感觉我很难短时间内 ...

  10. Javascript设计模式与开发实践读书笔记(1-3章)

    第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切 ...

随机推荐

  1. 掌握这些知识,你的python水平能更上一层楼

    今天讲一些python中的高级用法,有助于大家更好的使用python这门语言.今天讲的这些知识是层层递进的关系,前面是后面的铺垫. 函数可变参数*args和**kwargs python支持固定参数, ...

  2. WORD分栏后左右都能编辑

    操作如下: 如果是office的请参照:https://zhidao.baidu.com/question/403577041.html 如果是WPS:1.点击插入,有一个分页,点击之后下面有一个可选 ...

  3. linux里所有命令都不存在

    打开/etc/profile ,看这个配置文件里的PATH变量,遍历这个变量里配置的多个路径,查看命令是否存在. 主要是这四个:/bin ,/usr/bin,/sbin,/usr/sbin. PATH ...

  4. Django数据模型——通用字段选项

    字段 一个模型最重要也是唯一必需的部分,是它定义的数据库字段 字段名称限制 1.字段名不能是python保留字,这样会导致python语法错误 2.字段不能包含连续一个以上的下划线,这样会和Djang ...

  5. leetCode:461 汉明距离

    汉明距离 两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目. 给出两个整数 x 和 y,计算它们之间的汉明距离. 思路: 当看到"对应二进制位不同的位置的数目"这 ...

  6. python列表操作符

    list1=[123,456] list2=[234,234] list1>list2 >>>False#返回False 第一项比较之后直接返回false,第二项不看 #+实现 ...

  7. 笔记:MyBatis XML配置-typeAliases 内建别名表

    别名 映射的类型 _byte byte _long long _short short _int int _integer int _double double _float float _boole ...

  8. servlet实现方式(未完待续)

    servlet的是方式有三种,分别是: 1,实现servlt接口 点击查看详情 2,继承GenericServlet类[适配器模式] 3,继承HttpServlet类[模板方法设计模式]最常用的方法 ...

  9. mysql主从复制安装详解

    1.环境准备:两台Linux6.8服务器,(可以通过yum安装mysql:yum install mysql mysql-server -y,安装的版本可能比较低,对于练手来说够用了 )主库ip:19 ...

  10. 使用pm2躺着实现负载均衡

    事实上,pm2 是一个带有负载均衡功能的Node应用的进程管理器,Node实现进程管理的库有很多,forever也是其中一个很强大但是也相对较老的进程管理器. 为什么要使用pm2 对于这个问题,先说说 ...