一、定义
职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间 的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 示意图如图1所示。
二、实现
问题背景。
假设我们负责一个售卖手机的电商网站, 经过分别交纳 500 元定金和 200 元定金的两轮预定 后(订单已在此时生成) ,现在已经到了正式购买的阶段。 公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用 户会收到 100 元的商城优惠券, 200 元定金的用户可以收到 50 元的优惠券, 而之前没有支付定金 的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。
我们的订单页面是 PHP 吐出的模板,在页面加载之初,PHP会传递给页面几个字段。
- orderType:表示订单类型(定金用户或者普通购买用户) ,code 的值为 1 的时候是 500 元 定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户。
- pay:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的 订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。
- stock:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用 户不受此限制。
2.1 简单实现
// 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); // 输出:手机库存不足
2.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 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('手机库存不足'); }
};
var order = order500.after(order200).after(orderNormal);
order(1, true, 500); // 输出:500元定金预购,得到100优惠券
order(2, true, 500); // 输出:200元定金预购,得到50优惠券
order(1, false, 500); // 输出:普通购买,无优惠券
三、具体应用
我们前端页面的表单中,往往有很多校验。我们可以把这些校验连起来。比如我们现在需要对一个控件A进行两个校验(A1,A2)。我们可以对A先进行A1校验,如果A1校验通过了我们就传递给A2校验,这样当A1校验不通过时,我们就可以避免使用A2进行多余的校验。
/*职责链模式,便于参数校验,ok说明校验通过(当然,返回的参数是你自己定义的,你想定义为其他参数也行)*/
Function.prototype.after = function (fn) {
var self = this;
return function () {
var ret = self.apply(this, arguments);
//ok就向下校验
if (ret === 'ok') {
return fn.apply(this, arguments);
}
return ret;
}
}
function verifyCac516(obj) {
/*obj不能为空*/
function v1() {
if (isEmpty(obj.value)) {
Base.alert("档案出生日期为空", 'warn', function () {
Base.focus('cac516');
});
return 'error';
}
return 'ok';
}
/*性别不能为空*/
function v2() {
var gender = Base.getValue('aac004');
if (isEmpty(gender)) {
Base.alert("性别为空", 'warn', function () {
Base.focus('aac004');
});
return 'error';
}
return 'ok';
}
/*选择日期后计算其年龄并判断是否小于退休年龄并给予警告提示:
该人员年龄未达到退休年龄,请谨慎操作!提示后可以继续往下操作。*/
function v3(obj) {
var gender = Base.getValue('aac004');
var age = ages(obj.value);
if (gender === "1") {
if (age < txnlM) {
Base.alert("该人员年龄未达到退休年龄,请谨慎操作!")
}
} else if (gender === '2') {
if (age < txnlF) {
Base.alert("该人员年龄未达到退休年龄,请谨慎操作!");
}
}
}
var verify = v1.after(v2).after(v3);
verify(obj);
}
上面代码中,具体逻辑可以不用知道。只需要知道verifyCac516
对obj.value
进行了三个校验v1,v2,v3
,若其中任何一个校验失败了,就可以不用交个后面的校验函数进行校验了。
四、总结
优点。
- 解耦了请求发送者和 N 个接收者之间的复杂关 系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。
- 链中的节点对象可以灵活地拆分重组。增加或者删除一个节 点,或者改变节点在链中的位置都是轻而易举的事情。
- 可以手动指定起始节点,请求并不是非得从链中的第一个 节点开始传递。
缺点。
- 不能保证某个请求一定会被链中的节点处理。
- 职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分 节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避 免过长的职责链带来的性能损耗。