js中的职责链模式
定义
为了避免请求发送者与多个请求处理者耦合在一起,将这些处理者对象串成一条链。当有请求发生时,将该请求沿着这条链传递,直到有一个对象处理它为止。
详细描述
在现实生活中用户使用我们的产品遇到问题时,统一入口都是找客服,如果是咨询服务那么客服就直接处理,如果是技术性问题那么客服可能就会把问题转给前端,前端经过排查后发现不是前端的问题,在把问题转给后端,然后后端去排查是不是自己的问题。。。在这个处理用户问题的过程中根据各自不同的职责形成了一条链,也就是我们说的职责链。
回到我们平时开发项目时,会遇到大量不同场景的判断通常使用if else语句来逐个判断当前处于哪个场景下,但是那样代码会耦合非常的严重不利于维护和扩展,我们可以把所有的场景单独抽离出对应的函数去处理,最后在把这些处理函数串起来,让判断通过一个函数开始挨着去查找可以处理当前情况的函数直至最后处理完成, 这就是我们在开发情况下所用的职责链模式。
在很多实现中都有职责链的影子如: dom事件的冒泡处理, 过滤器, 用户权限处理等等。
代码实例
一个手机订购的开发场景,有一部分用户参加了订购之前预付定金活动,500元定金加送100元优惠券、200元定金加送50元优惠券,如果没有付定金的就不能任何享受优惠,还要根据库存情况是否能买到,预付定金成功的不受库存限制。现在订单信息接口会返回如下三个字段:
- orderType:表示订单类型(定金用户或者普通购买用户),code 的值为 1 的时候是 500 元 定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户。
- pay:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的 订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。
- stock:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用 户不受此限制。
首先看下不使用职责链模式的时候代码:
const order = function (orderType, pay, stock) {
if (pay) {
switch (orderType) {
case 1:
console.log('500 元定金预购, 得到 100 优惠券');
break;
case 2:
console.log('200 元定金预购, 得到 50 优惠券');
break;
}
} else {
if (stock > 0) {
console.log('普通购买成功, 无优惠券');
} else {
console.log('库存不足, 购买失败');
}
}
};
order(1, true, 500); // 500 元定金预购, 得到 100 优惠券
上述代码就是我们不使用职责链代码,我们需要根据不同的场景去写if else语句进行处理,如果还有其他场景和增加其他活动我们每次都需要深入这个处理函数修改相关的逻辑,不利于扩展和维护。 下面看下使用职责链模式实现:
// 定金500档处理函数
const order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay) {
console.log('500 元定金预购, 得到 100 优惠券');
} else {
// 将请求传递给200档处理函数
order200(orderType, pay, stock);
}
};
// 定金200档处理函数
const order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
// 将请求传递给普通档处理函数
orderNormal(orderType, pay, stock);
}
};
// 无定金档处理函数
const 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 元定金预购, 得到 50 优惠券
order500(3, false, 500); // 普通购买成功, 无优惠券
order500(3, false, 0); // 库存不足, 购买失败
上述代码拆分出不同的场景逻辑,把他们定义单独的处理函数(符合单一职责原则),并且把根据职责定义好的函数串联起来,这样我们就实现了基础的职责链模式。
但是当我们想要增加一个新的场景时(如定金300档活动),我们还是需要深入具体的处理函数中修改职责链的顺序非常的不灵活(不符合开放 – 封闭原则),所以下面我们继续改进我们的代码让它更加容易扩展:
// 定金500档处理函数
const order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay) {
console.log('500 元定金预购, 得到 100 优惠券');
} else {
// 传递请求
return 'nextSuccessor';
}
};
// 定金200档处理函数
const order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
// 传递请求
return 'nextSuccessor';
}
};
// 无定金档处理函数
const orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买成功, 无优惠券');
} else {
console.log('库存不足, 购买失败');
}
};
// 定义包装职责链节点的类
class Chain {
constructor(fn) {
this.fn = fn; // 当前处理函数
this.successor = null; // 下个节点处理函数
}
// 设置下个处理函数
setNextSuccessor(successor) {
return (this.successor = successor);
}
// 调用当前处理函数并传递请求
passRequest(...prames) {
const ret = this.fn.apply(this, prames);
if (ret === 'nextSuccessor')
return (
this.successor &&
this.successor.passRequest.apply(this.successor, prames)
);
return ret;
}
}
// 使用Chain类包装处理函数
const chainOrder500 = new Chain(order500);
const chainOrder200 = new Chain(order200);
const chainOrderNormal = new Chain(orderNormal);
// 指定职责链顺序
chainOrder500
.setNextSuccessor(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); // 库存不足, 购买失败
上述代码没有在当前处理函数中指定下个处理函数,而是约定了一个传递调用的字段,然后创建了一个包装类Chain它含有指定下个处理函数的方法setNextSuccessor用来指定处理函数的顺序,这时我们就实现了一个可以灵活自由的设置顺序的职责链模式。
此时我们想增加一个定金300档优惠券75的活动,代码如下:
// 定义300定金档逻辑
const order300 = function (orderType, pay, stock) {
if (orderType === 4 && pay) {
console.log('300 元定金预购, 得到 75 优惠券');
} else {
// 传递请求
return 'nextSuccessor';
}
};
// 包装一下
const chainOrder300 = new Chain(order300);
// 重新指定职责链顺序
chainOrder500
.setNextSuccessor(chainOrder300)
.setNextSuccessor(chainOrder200)
.setNextSuccessor(chainOrderNormal);
// 测试
chainOrder500.passRequest(1, true, 500); // 500 元定金预购, 得到 100 优惠券
chainOrder500.passRequest(2, true, 500); // 200 元定金预购, 得到 50 优惠券
chainOrder500.passRequest(4, true, 500); // 300 元定金预购, 得到 75 优惠券
chainOrder500.passRequest(3, true, 500); // 普通购买成功, 无优惠券
chainOrder500.passRequest(1, false, 0); // 库存不足, 购买失败
上述代码就是新增加了300定金活动代码,只需要新定义一个处理逻辑的函数,然后使用定义好的包装类Chain包装一下,最后重新指定一下新添加的处理函数在职责链中的位置即可,完全不需要去修改其他的处理函数。
异步职责链
有的场景下我们需要发请求实时从后端拿到数据后根据返回数据在处理是否传递调用下个节点,所以就涉及到异步问题,有两种解决方法1、使用await 使请求变为同步处理。2、包装类增加手动调用下个处理函数的方法 如:
// 定金500档处理函数
const order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay) {
console.log('500 元定金预购, 得到 100 优惠券');
} else {
// 传递请求
return 'nextSuccessor';
}
};
// 定金200档处理函数 - 异步
const order200 = function (orderType, pay, stock) {
// 模拟异步
const _self = this;
setTimeout(() => {
if (orderType === 2 && pay) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
_self.next(orderType, pay, stock);
}
});
};
// 无定金档处理函数
const orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买成功, 无优惠券');
} else {
console.log('库存不足, 购买失败');
}
};
// 定义包装职责链节点的类
class Chain {
constructor(fn) {
this.fn = fn; // 当前处理函数
this.successor = null; // 下个节点处理函数
}
// 设置下个处理函数
setNextSuccessor(successor) {
return (this.successor = successor);
}
// 调用当前处理函数并传递请求
passRequest(...prames) {
const ret = this.fn.apply(this, prames);
if (ret === 'nextSuccessor')
return (
this.successor &&
this.successor.passRequest.apply(this.successor, prames)
);
return ret;
}
// 手动调用下个节点函数
next(...prames) {
this.successor &&
this.successor.passRequest.apply(this.successor, prames);
}
}
// 使用Chain类包装处理函数
const chainOrder500 = new Chain(order500);
const chainOrder200 = new Chain(order200);
const chainOrderNormal = new Chain(orderNormal);
// 指定职责链顺序
chainOrder500
.setNextSuccessor(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); // 库存不足, 购买失败
上述代码将200定金档改为异步处理,需要等数据回来后主动调用下个节点处理函数。
总结
优点:
- 解耦请求者与处理者之间的关系。
- 可以动态的自由的添加节点处理函数,易于扩展。
- 明确各函数的责任范围,符合单一职责原则。
缺点:
- 职责链太多,处理时间过长可能会对性能造成影响。
- 需要增加额外的包装对象去维护各个处理函数,会导致系统复杂性增加。