【设计模式】使用中介者模式优化表单交互

2024/04/24 11:54:372022/12/27 11:15:22

我们想象一下机场的指挥塔,如果没有指挥塔的存在,每一架飞机要和方圆 100 公里内的所有飞机通信,才能确定航线以及飞行状况,后果是不可想象的。现实中的情况是,每架飞机都只需要和指挥塔通信。指挥塔作为调停者,知道每一架飞机的飞行状况,所以它可以安排所有飞机的起降时间,及时做出航线调整。

什么是中介者模式?

在程序里,也许一个对象会和其他 10 个对象打交道,所以它会保持 10 个对象的引用,并且自己维护与其他对象的交互逻辑。

当程序的规模增大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用。

复杂的多对多关系

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用所以当一个对象发生改变时,只需要通知中介者对象即可。

在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方。

更清晰的一对多关系

中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。

各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。

示例:要素之间相互影响的商品表单

假设一个商品选择表单有如下功能:

  • 剩余数量受所选颜色和内存影响。
  • 购买数量超过剩余数量时提交按钮置灰。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <form id="form">
      <div>
        <label for="color">颜色</label>
        <select name="color" id="color">
          <option value="red">red</option>
          <option value="blue">blue</option>
          <option value="green">green</option>
        </select>
      </div>
      <div>
        <label for="memory">内存</label>
        <select name="memory" id="memory">
          <option value="red">2G</option>
          <option value="blue">4G</option>
          <option value="green">8G</option>
        </select>
      </div>
      <div>
        <label for="num">购买数量</label>
        <input name="num" type="number" id="num" />
      </div>
      <div>
        <span>剩余数量:</span>
        <span id="surplus"></span>
      </div>
      <button id="btn">提交</button>
    </form>

    <script>
      const form = document.getElementById("form");
      const numInput = form.num;
      const colorSelect = form.color;
      const colorMemory = form.memory;
      const btn = form.btn;
      const surplus = document.getElementById("surplus");

      // 根据参数获取剩余数量
      const commodityMap = {};
      function getCommodityNum(color, memory) {
        const key = `${color}-${memory}`;
        if (!commodityMap[key]) {
          commodityMap[key] = Math.floor(Math.random() * 10);
        }
        return commodityMap[key];
      }

      // 更新剩余数量
      function updateSurplus() {
        const num = getCommodityNum(colorSelect.value, colorMemory.value);
        surplus.innerText = num;
      }

      // 更新按钮状态
      function updateBtnDisabledStatus() {
        if (Number(surplus.innerText) < Number(numInput.value)) {
          btn.setAttribute("disabled", true);
        } else {
          btn.removeAttribute("disabled");
        }
      }

      numInput.oninput = function () {
        updateBtnDisabledStatus();
      };

      colorSelect.onchange = function () {
        updateSurplus();
        updateBtnDisabledStatus();
      };

      colorMemory.onchange = function () {
        updateSurplus();
        updateBtnDisabledStatus();
      };

      btn.onclick = function (e) {
        const params = {
          num: numInput.value,
          color: colorSelect.value,
          memory: colorMemory.value,
        };
        console.log(params);
        e.preventDefault();
      };

      updateSurplus();
    </script>
  </body>
</html>

最终效果如下:

中介者模式-示例-1中介者模式-示例-2

这样的实现方式,需要在每一个表单项的 onchange 事件中维护两种事件:更新按钮状态更新剩余数量

这种写法的弊端在于需要牢记每个表单项之间的关联关系,在后续有变更的情况下要在多处进行修改,同时也产生了一些重复代码。

如果后续需求变动,要再加一个 cpu 的选择,那就要将 onchange 事件处理程序再粘贴一份出来:

// ...
cpuSelect.onchange = function () {
  updateSurplus();
  updateBtnDisabledStatus();
};
// ...

用中介者模式优化

引入一个中介者类来集中定义处理程序。

// ...
class Mediator {
  static change(target) {
    updateBtnDisabledStatus();
    if (target != numInput) {
      updateSurplusText();
    }
  }
}

// ...
numInput.oninput = function () {
  Mediator.change(this);
};

colorSelect.onchange = function () {
  Mediator.change(this);
};

colorMemory.onchange = function () {
  Mediator.change(this);
};
// ...

这样改动之后,不需要再在表单项的 onchange 事件中处理 更新按钮状态更新剩余数量,而是只触发 Mediator.change 方法。

Mediator.change 方法中根据触发事件的对象来区分要执行什么操作。

这时如果要加一个 cpu 选择,可以添加如下代码:

// ...
cpuSelect.onchange = function () {
  Mediator.change(this);
};
// ...

可以看到这样的改动对于整体而言改动较小,并且 cpuSelect 对象不需要关注它的改动会造成什么影响。

总结

中介者模式的优点是解除了对象之间的紧密耦合关系,在新建对象以及新建对象关系时提供更高的可维护性和可扩展性。

它的缺点在于它将对象之间交互的复杂性转移成了中介者对象的复杂性,使得中介者对象经常是巨大的,中介者对象自身往往就是一个难以维护的对象

是否使用中介者模式取决于对象之间的耦合程度,毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌模式和过度设计。

参考

《JavaScript 设计模式与开发实践》