职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
职责链模式的最大优点:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系
无论是作用域链、原型链,还是DOM节点中的事件冒泡,我们都能从中找到职责链模式的影子。职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率
一个例子
假设魅族 16 的网购,客户可选择订单模式,第一种是缴纳定金 500,之后则不会因为库存而买不到货,并且会获得优惠卷 200 优惠卷;第二种是缴纳定金 200,之后则不会因为库存而买不到货,并且会获得优惠卷 100 优惠卷;第三种是普通购买模式,即不交定金,可能缺货,且没有优惠卷,其中选择第一种或第二种模式后如果不交定金,则会变为第三种模式
对于客户会是怎样的购买模式,如果不使用设计模式,就会有很多分枝判断语句,而且没有解耦,不符合开封封闭原则
当让这种情况可以用策略模式来优化,但这次来看使用职责链模式怎么优化:
1const order500 = function (orderType, hasPaid, stock) {2 if (orderType === 1 && hasPaid) {3 console.log('500 元定金预购,得到 200 优惠卷')4 } else {5 order200(orderType, hasPaid, stock)6 }7}8const order200 = function (orderType, hasPaid, stock) {9 if (orderType === 2 && hasPaid) {10 console.log('200 元定金预购,得到 100 优惠卷')11 } else {12 orderNormal(orderType, hasPaid, stock)13 }14}15const orderNormal = function (orderType, hasPaid, stock) {16 if (stock > 0) {17 console.log('普通购买,无优惠卷')18 } else {19 console.log('库存不足')20 }21}2223order500(1, true, 500) // 500 元定金预购,得到 200 优惠卷24order500(2, false, 500) // 普通购买,无优惠卷25order500(3, false, 0) // 库存不足
虽然相较于也一堆条件分支的结构要清晰很多,但请求在链条传递中非常僵硬,传递请求的代码耦合在业务函数中,依然违反开放封闭原则,如果有一天要增加、拆除或者移动一个节点,就必须得先砸烂这根链条
1const order500 = function (orderType, hasPaid, stock) {2 if (orderType === 1 && hasPaid) {3 console.log('500 元定金预购,得到 200 优惠卷')4 return false5 }6 return true7}8const order200 = function (orderType, hasPaid, stock) {9 if (orderType === 2 && hasPaid) {10 console.log('200 元定金预购,得到 100 优惠卷')11 return false12 }13 return true14}15const orderNormal = function (orderType, hasPaid, stock) {16 if (stock > 0) {17 console.log('普通购买,无优惠卷')18 } else {19 console.log('库存不足')20 }21}2223// 通用的职责链24class Node {25 constructor(fn) {26 this.fn = fn // 职责27 this.nextNode = null28 }2930 setNextNode(node) {31 this.nextNode = node32 }3334 passReq(...args) {35 const needPassReq = this.fn(...args)36 if (needPassReq) {37 return this.nextNode && this.nextNode.passReq(...args)38 }39 return needPassReq40 }41}4243const order500Node = new Node(order500)44const order200Node = new Node(order200)45const orderNormalNode = new Node(orderNormal)4647order500Node.setNextNode(order200Node)48order200Node.setNextNode(orderNormalNode)4950order500Node.passReq(1, true, 500)51order500Node.passReq(2, false, 500)52order500Node.passReq(3, false, 0)
结果同上,而且当我们可以灵活的修改和扩展,符合开放封闭原则,比如在最开始增加一个开始判断订单类型的职责,这对用户体验可能好点:
1// ...2const startJudge = function (orderType, hasPaid, stock) {3 console.log('开始判断您的订单类型')4 return true5}67const startJudgeNode = new Node(startJudge)8startJudgeNode.setNextNode(order500Node)910startJudgeNode.passReq(1, true, 500)11startJudgeNode.passReq(2, false, 500)12startJudgeNode.passReq(3, false, 0)
异步的职责链
在开发中,我们经常会遇到一些异步的问题,比如我们要在节点函数中发起一个 ajax 异步请求,异步请求返回的结果才能决定是否继续在职责链中 passReq,这时就不需要让节点同步的返回 nextNode,我们增加一个 next 方法,手动返回 nextNode
在 Promise 中,resolve 就相当于 next 方法,这里我们写一个更简单的 Promise,简单到变形,但相比之前简易版的更强大(当然没有原生的强大)
1class PNode {2 constructor(fn) {3 this.fn = fn4 this.nextNode = null5 }67 setNext(node) {8 return this.nextNode = node9 }1011 next(...args) { // next 传递下一个节点 fn 的参数;next 由 fn 触发:1.function () { this.next() } 2.() => { p1.next() }12 return this.nextNode && this.nextNode.start(...args)13 }1415 start(...args) { // start 传递这个 fn 的参数16 this.fn(...args)17 }18}1920const p1 = new PNode(function (a) {21 console.log('开始执行')22 setTimeout(() => {23 console.log(a++)24 this.next(a, 'hah')25 }, 2000)26})27const p2 = new PNode((b, str) => {28 console.log(b, str)29 p2.next(b + str)30})31const p3 = new PNode((c) => {32 setTimeout(() => {33 console.log(c)34 console.log('执行完毕')35 }, 1000)36})3738p1.setNext(p2).setNext(p3)39p1.start(0)
AOP 实现职责链
利用JavaScript的函数式特性,有一种更加方便的方法来创建职责链
1Function.prototype.after = function (fn) {2 const self = this3 return function () {4 const needPassReq = self.apply(this, arguments)5 if (needPassReq){6 return fn.apply(this, arguments)7 }8 return ret9 }10}1112const order = order500yuan.after(order200yuan).after(orderNormal)1314order(1, true, 500)15order(2, false, 500)16order(3, false, 0)
JavaScript 中,函数也是对象,由此让它支持异步:
1Function.prototype.setNext = function (fn) {2 return this.nextFn = fn3}4Function.prototype.next = function (...args) { // 只支持 p1.next()5 return this.nextFn && this.nextFn(...args)6}7Function.prototype.start = function (...args) {8 this(...args)9}1011const p1 = function (a) {12 console.log('开始执行')13 setTimeout(() => {14 console.log(a++)15 p1.next(a, 'hah')16 }, 2000)17}18const p2 = (b, str) => {19 console.log(b, str)20 p2.next(b + str)21}22const p3 = (c) => {23 setTimeout(() => {24 console.log(c)25 console.log('执行完毕')26 }, 1000)27}28p1.setNext(p2).setNext(p3)29p1.start(0)
优缺点
优点:
解耦了请求发送者和N个接收者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可
链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情
可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递
缺点:
我们不能保证某个请求一定会被链中的节点处理,在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求
使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗