最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript 设计模式之职责链模式

    正文概述 掘金(牛牛_lz)   2021-08-22   513

    这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

    现实中的职责链模式

    1. 高峰期间坐公交车的车票问题,需要经过N个人手上传递才可以到达售票员的手里。
    2. 考试时,遇到不会答的题目,就把题目编号写在小纸条上往后传递,坐在后面的同学如果也不会答,他就会把这张小纸条继续递给他后面的人。

    从这两个示例可以找到职责链模式的最大优点:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。

    实际开发中的职责链模式

    促销时针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

    接收一下三个参数:

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

    接下来把这个流程写成代码:

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

    用职责链模式重构代码

    // 500 元订单
    var order500 = function (orderType, pay, stock) {
      if (orderType === 1 && pay === true) {
        console.log('500 元定金预购, 得到 100 优惠券');
      } else {
        order200(orderType, pay, stock); // 将请求传递给 200 元订单 
      }
    };
    // 200 元订单
    var order200 = function (orderType, pay, stock) {
      if (orderType === 2 && pay === true) {
        console.log('200 元定金预购, 得到 50 优惠券');
      } else {
        orderNormal(orderType, pay, stock); // 将请求传递给普通订单 }
      };
    }
    // 普通购买订单
    var orderNormal = function (orderType, pay, stock) {
      if (stock > 0) {
        console.log('普通购买, 无优惠券');
      } else {
        console.log('手机库存不足');
      }
    };
    // 测试结果:
    order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
    order500(1, false, 500); // 输出:普通购买, 无优惠券
    order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
    order500(3, false, 500); // 输出:普通购买, 无优惠券
    order500(3, false, 0);// 输出:手机库存不足
    

    可以看到,执行结果和之前的 order 函数完全一样,但是代码的结构已经清晰很多,我们把一个大函数拆分了3个小函数,去掉了许多嵌套的条件分支语句。

    但是,还有不足之处,可以看到请求在链条中的顺序非常僵硬,传递请求的代码被耦合在了业务函数中。这依然是违反开放-封闭原则的,如果有一天要增加300元预定或者去掉200元预定,以为着必须修改这些业务函数内部。就像一根环环相扣打了死结的链条,如果要增加、拆除或者移动一个节点,就必须得先砸烂这根链条

    灵活可拆分的职责链节点

    首先改写下分别表示3种购买模式的节点函数,如果某个节点不能处理请求,则返回一个特定的字符串“nextSuccessor”来表示该请求需要继续往后面传递。

    // 500 元订单
    var order500 = function (orderType, pay, stock) {
      if (orderType === 1 && pay === true) {
        console.log('500 元定金预购, 得到 100 优惠券');
      } else {
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
      }
    };
    // 200 元订单
    var order200 = function (orderType, pay, stock) {
      if (orderType === 2 && pay === true) {
        console.log('200 元定金预购, 得到 50 优惠券');
      } else {
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
      };
    }
    // 普通购买订单
    var orderNormal = function (orderType, pay, stock) {
      if (stock > 0) {
        console.log('普通购买, 无优惠券');
      } else {
        console.log('手机库存不足');
      }
    };
    

    接下来需要把函数包装进职责链节点,我们定义一个构造函数 Chain,在 new Chain 的时候传递的参数即为需要被包装的函数,同时它还拥有一个实例属性 this.successor,表示在链中的下一个节点。

    var Chain = function (fn) {
      this.fn = fn;
      this.successor = null;
    };
    // 指定在链中的下一个节点
    Chain.prototype.setNextSuccessor = function (successor) {
      return this.successor = successor;
    };
    // 传递请求给某个节点
    Chain.prototype.passRequest = function () {
      var ret = this.fn.apply(this, arguments);
      if (ret === 'nextSuccessor') {
        return this.successor && this.successor.passRequest.apply(this.successor, arguments);
      } return ret;
    };
    

    接下来,把3个订单函数分别包装成职责链的节点:

    var chainOrder500 = new Chain(order500);
    var chainOrder200 = new Chain(order200);
    var chainOrderNormal = new Chain(orderNormal);
    

    然后指定节点在职责链中的顺序:

    chainOrder500.setNextSuccessor(chainOrder200);
    chainOrder200.setNextSuccessor(chainOrderNormal);
    

    最后把请求传递给第一个节点:

    chainOrder500.passRequest(1, true, 500);// 输出:500 元定金预购,得到 100 优惠券
    chainOrder500.passRequest(2, true, 500);// 输出:200 元定金预购,得到 50 优惠券 
    chainOrder500.passRequest(3, true, 500);// 输出:普通购买,无优惠券
    chainOrder500.passRequest(1, false, 0);// 输出:手机库存不足
    

    通过改进,我们可以自由灵活地增加、移除和修改链中的节点顺序,假设某天需要支持 300 元定金购买,可以在该链中增加一个节点即可:

    var order300 = function () {
        // 具体实现略
    };
    chainOrder300 = new Chain(order300); 
    chainOrder500.setNextSuccessor(chainOrder300); 
    chainOrder300.setNextSuccessor(chainOrder200);
    

    异步的职责链

    在上述的职责链模式中,我们让每个节点函数同步返回一个特定的值 nextSuccessor ,来表示是否把请求传递给下一个节点。而在现实开发中,我们经常会遇到一些异步的问题,比如我们要在节点函数中发起一个 ajax 异步请求,异步请求返回的结果才能决定是否继续在职责链中 passRequest。此时,让节点函数同步返回 nextSuccessor 已经没有意义了,所以要给 Chain 类再增加一个原型方法 Chain.prototype.next,表示手动传递请求给职责链中的下一个节点:

    Chain.prototype.next = function () {
      return this.successor && this.successor.passRequest.apply(this.successor, arguments);
    }
    

    接下来,写一个异步职责链:

    var fn1 = new Chain(function () {
      console.log(1);
      return 'nextSuccessor';
    });
    var fn2 = new Chain(function () {
      console.log(2);
      var self = this; setTimeout(function () {
        self.next();
      }, 1000);
    });
    var fn3 = new Chain(function () {
      console.log(3);
    });
    fn1.setNextSuccessor(fn2).setNextSuccessor(fn3); 
    fn1.passRequest();
    

    职责链模式的优缺点

    优点

    1. 解耦了请求发送者和 N 个接收者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。
    2. 链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情。
    3. 可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递。

    缺点

    1. 不能保证某个请求一定会被链中的节点处理。在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。
    2. 职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗。

    用 AOP 实现职责链

    Function.prototype.after = function (fn) {
      var self = this;
      return function () {
        var ret = self.apply(this, arguments);
        if (ret === 'nextSuccessor') {
          return fn.apply(this, arguments);
        }
        return ret;
      }
    };
    var order = order500yuan.after(order200yuan).after(orderNormal);
    order(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
    order(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
    order(1, false, 500);// 输出:普通购买,无优惠券
    

    最后说一句

    如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。


    起源地下载网 » JavaScript 设计模式之职责链模式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元