假设项目中有这样一个需求:
- orderType 参数的值有三种情况
- === 1 时表示 500 定金
- === 2 时表示 200 定金
- === 3 是表示 无优惠券,原价购买
- pay 表示是否支付定金
- stack 表示还有多少库存
那么根据需求大概可以写出这样的代码:
function order(orderType, pay, stack) {
if (orderType === 1) { // 500 定金
if (pay === true) {
console.log('支付 500 定金成功')
} else {
if (stack > 0) {
console.log('普通订单, 无优惠券')
} else {
console.log('库存不足')
}
}
} else if (orderType === 2) { // 200 定金
if (pay === true) {
console.log('支付 200 定金成功')
} else {
if (stack > 0) {
console.log('普通订单, 无优惠券')
} else {
console.log('库存不足')
}
}
} else if (orderType === 3) {
if (stack > 0) {
console.log('普通订单, 无优惠券')
} else {
console.log('库存不足')
}
}
}
order(1, true, 10)
order(2, false, 10)
order(3, true, 0)
这么做确实可以完成需求, 但是代码并不好看, 同时层级嵌套也比较深, 用了大量的 if/else. 根据这种场景就引申除了这次的主题: 责任链模式.
责任链模式 : 每个函数只处理一种场景的情况(类似单一职责), 如果不符合条件则转交个下一个方法处理, 这种串联的关系就是责任链模式.
根据责任链原则, 可以得出第一版代码:
function order500(orderType, pay, stack) {
if (orderType === 1 && pay === true) {
console.log('支付 500 定金成功')
} else {
// orderNormal(orderType, pay, stack)
return 'next'
}
}
function order200(orderType, pay, stack) {
if (orderType === 2 && pay === true) {
console.log('支付 200 定金成功')
} else {
// orderNormal(orderType, pay, stack)
return 'next'
}
}
function orderNormal(orderType, pay, stack) {
if (stack > 0) {
console.log('普通订单, 无优惠券')
} else {
console.log('库存不足')
}
}
function Chain(fn) {
this.fn = fn
this.next = null
}
Chain.prototype.set = function (fn) {
this.next = fn
}
Chain.prototype.run = function () {
const result = this.fn.apply(this, arguments)
// 如果得到的返回值是 next, 则转交下一个方法处理
if (result === 'next') {
// 如果没有下一个方法, 则会异常
return this.next && this.next.run.apply(this.next, arguments)
}
}
// 包装函数, 保证每次调用都会返回一个新的 Chain 对象
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)
// 设定链条关系
chainOrder500.set(chainOrder200)
chainOrder200.set(chainOrderNormal)
// 运行这个链条
chainOrder500.run(1, true, 10)
chainOrder500.run(2, false, 10)
chainOrder500.run(3, true, 0)
但这种方式还是比较麻烦, 需要对每一个方法进行包装, 可以在 Function
的原型上添加额外的方法, 使用链式调用的方式来优化这个问题.
Function.prototype.after = function (fn) {
const self = this
return function () {
const result = self.apply(this, arguments)
return result === 'next'
? fn.apply(this, arguments)
: result
}
}
var order = order500.after(order200).after(orderNormal)
order(1, true, 10)
order(2, false, 10)
order(3, true, 0)
这么做确实解决了问题, 但是日常开发中不应该去给内置构造函数添加方法, 因为这会导致后续接手代码的人很困惑, “为什么这个函数可以直接调用方法, 却没看到定义?”, 所以我们可以自己定义一个构造函数, 然后在原型上添加方法, 最后用实例化后的对象来操作:
function Chain2() {
this.chain = []
}
Chain2.prototype.add = function (fn) {
this.chain.push(fn)
return this
}
Chain2.prototype.run = function () {
const self = this
const args = arguments
for (let i = 0; i < this.chain.length; i++) {
const result = this.chain[i].apply(this, args)
if (result === 'next') {
continue
} else {
return result
}
}
}
const chain = new Chain2()
chain.add(order500).add(order200).add(orderNormal)
chain.run(1, true, 10)
chain.run(2, false, 10)
chain.run(3, true, 0)
以上.