职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点,如图所示。
场景: 某电商针对已付过定金的用户有优惠政策, 在正式购买后, 已经支付过 500 元定金的用户会收到 100 元的优惠券, 200 元定金的用户可以收到 50 元优惠券, 没有支付过定金的用户只能正常购买。
// orderType: 表示订单类型, 1: 500 元定金用户;2: 200 元定金用户;3: 普通购买用户
// pay: 表示用户是否已经支付定金, true: 已支付;false: 未支付
// stock: 表示当前用于普通购买的手机库存数量, 已支付过定金的用户不受此限制
const order = function( orderType, pay, stock ) {
if ( orderType === 1 ) {
if ( pay === true ) {
console.log('500 元定金预购, 得到 100 元优惠券')
} else {
if (stock > 0) {
console.log('普通购买, 无优惠券')
} else {
console.log('库存不够, 无法购买')
}
}
} else if ( orderType === 2 ) {
if ( pay === true ) {
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( 3, true, 500 ) // 普通购买, 无优惠券
职责链模式改造代码:
const order500 = function(orderType, pay, stock) {
if ( orderType === 1 && pay === true ) {
console.log('500 元定金预购, 得到 100 元优惠券')
} else {
order200(orderType, pay, stock)
}
}
const order200 = function(orderType, pay, stock) {
if ( orderType === 2 && pay === true ) {
console.log('200 元定金预购, 得到 50 元优惠券')
} else {
orderCommon(orderType, pay, stock)
}
}
const orderCommon = function(orderType, pay, stock) {
if (orderType === 3 && stock > 0) {
console.log('普通购买, 无优惠券')
} else {
console.log('库存不够, 无法购买')
}
}
order500( 3, true, 500 ) // 普通购买, 无优惠券
以上例子的问题:
请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中:
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购, 得到 100 优惠券' );
}else{
order200( orderType, pay, stock );
// order200 和 order500 耦合在一起
}
};
可通过构造链接节点(Chain),让链中的各个节点可以灵活拆分和重组。
var order500 = function(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购,得到 100 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
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.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
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); // 输出:手机库存不足
用 AOP 实现的职责链
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
在 JavaScript 中实现 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); // 输出:普通购买,无优惠券
职责链模式优缺点
优点
职责链模式的最大优点就是解耦了请求发送者和 N 个接收者之间的复杂关系,
使用了职责链模式之后,链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情。
那就是可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递。(组合模式中,固定了一条链条,需要遍历所有组合对象,从而影响性能)
缺点
我们不能保证某个请求一定会被链中的节点处理。
在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求
职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗。
总结
在 JavaScript 开发中,职责链模式是最容易被忽视的模式之一。实际上只要运用得当,职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。职责链中的节点数量和顺序是可以自由变化的,我们可以在运行时决定链中包含哪些节点。
无论是作用域链、原型链,还是 DOM 节点中的事件冒泡,我们都能从中找到职责链模式的影子。职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。学会使用职责链模式,相信在以后的代码编写中,将会对你大有裨益。