浅析-特定场景下if-else的优化方案(下)

86 阅读8分钟

上篇讲到维护工作变得复杂。可以进行以下尝试:

  • 初步优化

       现在我们可以将不同的订单处理逻辑(也就是节点)提取出来,先把 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 事件流的事件冒泡,都有责任链模式的影子

  1. 作用域链:查找变量时,先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象;
  2. 原型链:当读取实例的属性时,如果找不到,就会查找当前对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止;
  3. 事件冒泡: 事件在 DOM 元素上触发后,会从最内层的元素开始发生,一直向外层元素传播,直到全局 document 对象;

       以事件冒泡为例,事件在某元素上触发后,会一级级往外层元素传递事件,如果当前元素没有处理这个事件并阻止冒泡,那么这个事件就会往外层节点传递,就像请求在责任链中的职责节点上传递一样,直到某个元素处理了事件并阻止冒泡。

  • 责任链的优点
  1. 由于处理请求的职责节点可能是职责链上的任一节点,所以请求的发送者和接受者是解耦的;
  2. 通过改变链内的节点或调整节点次序,可以动态地修改责任链;

  • 责任链的缺点
  1. 并不能保证请求一定会被处理,有可能到最后一个节点还不能处理;
  2. 调试不便,调用层次会比较深,也有可能会导致循环引用;
  • 责任链的适用场景
  1. 需要多个对象处理同一个请求,具体该请求由哪个对象处理在运行时才确定;
  2. 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式;
  3. 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式;

小结

       事实上,大部分业务需求都能使用最基本的if-else完成,只不过是简单和繁琐的问题,当我们遇到这类问题,不妨改变一下思路,将自己的代码优雅化,使用上数据配置或者责任链模式,你会发现不仅仅是思路的扩展,代码的逻辑都清晰了不少,更方便维护。

参考文章: 如何对多个 if else 优化 JavaScript 复杂判断的更优雅写法 初探javascript设计模式-责任链模式