上篇讲到维护工作变得复杂。可以进行以下尝试:
- 初步优化
现在我们可以将不同的订单处理逻辑(也就是节点)提取出来,先把 500 元订单、200 元订单以及普通购买分成 3个函数。接下来把 orderType、pay、stock 这 3 个字段当作参数传递给 500 元订单函数,如果该函数不符合处理条件,则把这个请求传递给后面的 200 元订单函数,如果 200 元订单函数依然不能处理该请求,则继续传递请求给普通购买函数
// 500元的订单
let order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500元定金,得到100元优惠券");
} else {
order200(orderType, pay, stock); //将请求转给200元订单
}
};
//200元订单
let order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200元定金预约,得到50优惠券");
} else {
orderNormal(orderType, pay, stock); //将请求传给普通订单
}
};
//普通购买订单
let 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); // 手机库存不足
可以看到,执行结果和前面那个巨大的 order 函数完全一样,但是代码的结构已经清晰了很多,我们把一个大函数拆分了 3 个小函数,去掉了许多嵌套的条件分支语句。目前已经有了不小的进步,但我们不会满足于此,虽然已经把大函数拆分成了互不影响的 3个小函数,但可以看到,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中。
如果有天我们要增加 300 元预订或者去掉 200 元预订,意味着就必须改动这些业务函数内部。就像一根环环相扣打了死结的链条,如果要增加、拆除或者移动一个节点,那么就要同时改动相邻的职责节点函数。
这时我们可以引入责任链模式,将职责节点的下一个节点使用拼接的方式,而不是在声明的时候就固定。这里我们:
// 500元订单
let Order500 = {
nextOrder: null,
setNext: function (next) {
this.nextOrder = next;
},
handle: function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500元定金,得到100元优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
},
};
// 200元订单
let Order200 = {
nextOrder: null,
setNext: function (next) {
this.nextOrder = next;
},
handle: function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200元定金预约,得到50优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
},
};
// 普通订单
let OrderNormal = {
nextOrder: null,
setNext: function (next) {
this.nextOrder = next;
},
handle: function (orderType, pay, stock) {
if (stock > 0) {
console.log("普通购买,没有优惠券");
} else {
console.log("手机库存不足");
}
},
};
// 设置500元的订单的下一个节点为200元订单
Order500.setNext(Order200);
// 设置200元订单的下一个节点为普通购买订单
Order200.setNext(OrderNormal);
Order500.handle(1, true, 500); // 500元定金,得到100元优惠券
Order500.handle(1, false, 500); // 普通购买,没有优惠券
Order500.handle(2, true, 500); // 200元定金预约,得到50优惠券
Order500.handle(3, false, 500); // 普通购买,没有优惠券
Order500.handle(3, false, 0); // 手机库存不足
这样,将订单处理的链在使用的时候再拼起来,灵活性好,比如如果要在200元订单和普通中间增加一个新的节点,那么在使用时:
// 新的订单节点
let OrderNew = {
nextOrder: null,
setNext: function (next) {
this.nextOrder = next;
},
handle: function (orderType, pay, stock) {},
};
// 设置500元的订单的下一个节点为200元订单
Order500.setNext(Order200);
// 设置200元订单的下一个节点为新的订单处理节点
Order200.setNext(OrderNew);
// 设置新的订单处理节点的下一个节点为普通购买订单
OrderNew.setNext(OrderNormal);
删除节点也是类似操作,给维护带来很大方便。
但是我们看到之前的内容有很多重复代码,比如 Order 对象里的 nextOrder、setNext 里的逻辑就是一样的,可以用继承来避免这部分重复。
// 订单基类
class Order {
constructor() {
this.nextOrder = null;
}
setNext(next) {
this.nextOrder = next;
}
}
// 500元订单
class Order500 extends Order {
handle(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500元定金,得到100元优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
}
}
// 200元订单
class Order200 extends Order {
handle(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200元定金预约,得到50优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
}
}
// 普通订单
class OrderNormal extends Order {
handle(orderType, pay, stock) {
if (stock > 0) {
console.log("普通购买,没有优惠券");
} else {
console.log("手机库存不足");
}
}
}
const order1 = new Order500();
const order2 = new Order200();
const order3 = new OrderNormal();
// 设置500元的订单的下一个节点为200元订单
order1.setNext(order2);
// 设置200元订单的下一个节点为普通购买订单
order2.setNext(order3);
order1.handle(1, true, 500); // 500元定金,得到100元优惠券
order1.handle(1, false, 500); // 普通购买,没有优惠券
order1.handle(2, true, 500); // 200元定金预约,得到50优惠券
order1.handle(3, false, 500); // 普通购买,没有优惠券
order1.handle(3, false, 0); // 手机库存不足
- 使用链模式优化
之前的代码实现,我们可以使用链模式稍加重构,在设置下一个职责节点的方法 setNext 中返回下一个节点实例,使得在责任链的组装过程是一个链的形式,代码结构更加简洁。
// 订单基类
class Order {
constructor() {
this.nextOrder = null;
}
setNext(next) {
this.nextOrder = next;
return next
}
}
// 500元订单
class Order500 extends Order {
handle(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500元定金,得到100元优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
}
}
// 200元订单
class Order200 extends Order {
handle(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200元定金预约,得到50优惠券");
} else {
this.nextOrder.handle(orderType, pay, stock);
}
}
}
// 普通订单
class OrderNormal extends Order {
handle(orderType, pay, stock) {
if (stock > 0) {
console.log("普通购买,没有优惠券");
} else {
console.log("手机库存不足");
}
}
}
const order1 = new Order500();
const order2 = new Order200();
const order3 = new OrderNormal();
// 组装处理链
// 设置500元的订单的下一个节点为200元订单
// 设置200元订单的下一个节点为普通购买订单
order1.setNext(order2).setNext(order3)
order1.handle(1, true, 500); // 500元定金,得到100元优惠券
order1.handle(1, false, 500); // 普通购买,没有优惠券
order1.handle(2, true, 500); // 200元定金预约,得到50优惠券
order1.handle(3, false, 500); // 普通购买,没有优惠券
order1.handle(3, false, 0); // 手机库存不足
责任链模式可能在真实的前端业务代码中见的不多,但是作用域链、原型链、DOM 事件流的事件冒泡,都有责任链模式的影子
- 作用域链:查找变量时,先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象;
- 原型链:当读取实例的属性时,如果找不到,就会查找当前对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止;
- 事件冒泡: 事件在 DOM 元素上触发后,会从最内层的元素开始发生,一直向外层元素传播,直到全局 document 对象;
以事件冒泡为例,事件在某元素上触发后,会一级级往外层元素传递事件,如果当前元素没有处理这个事件并阻止冒泡,那么这个事件就会往外层节点传递,就像请求在责任链中的职责节点上传递一样,直到某个元素处理了事件并阻止冒泡。
- 责任链的优点
- 由于处理请求的职责节点可能是职责链上的任一节点,所以请求的发送者和接受者是解耦的;
- 通过改变链内的节点或调整节点次序,可以动态地修改责任链;
- 责任链的缺点
- 并不能保证请求一定会被处理,有可能到最后一个节点还不能处理;
- 调试不便,调用层次会比较深,也有可能会导致循环引用;
- 责任链的适用场景
- 需要多个对象处理同一个请求,具体该请求由哪个对象处理在运行时才确定;
- 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式;
- 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式;
小结
事实上,大部分业务需求都能使用最基本的if-else完成,只不过是简单和繁琐的问题,当我们遇到这类问题,不妨改变一下思路,将自己的代码优雅化,使用上数据配置或者责任链模式,你会发现不仅仅是思路的扩展,代码的逻辑都清晰了不少,更方便维护。
参考文章: 如何对多个 if else 优化 JavaScript 复杂判断的更优雅写法 初探javascript设计模式-责任链模式