JavaScript 设计模式之中介者模式

1,206 阅读7分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

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

现实中的中介者

1 机场指挥者

中介者也被称为调停者。现实中的情况是,每架飞机都只需要和指挥塔通信。指挥塔作为调停者,知道每一架飞机的飞行状况,所以它可以安排所有飞机的起降时间,及时做出航线调整。

2 博彩公司

在世界杯期间购买足球彩票,博彩公司作为中介,每个人只需和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就交给博彩公司。

中介者模式的例子——购买商品

需求:实现购买手机的页面,在购买流程中,可以选择手机的颜色以及输入购买数量,同时页面中有两个展示区域,分别向用户展示刚刚选择好的颜色和数量。还有一个按钮动态显示下一步的操作,我们需要查询该颜色手机对应的库存,如果库存数量少于这次的购买数量,按钮将被禁用并且显示库存不足,反之按钮可以点击并且显示放入购物车。

假设手机库存为:

var goods = {
  "red": 3,
  "blue": 6
}

那么页面中,会有一下几种场景:

  1. 选择红色手机,购买 4 个,库存不足。
  2. 选择蓝色手机,购买 5 个,库存充足,可以加入购物车。
  3. 没有输入购买数量的时候,按钮将被禁用并显示相应提示。

那么基本上至少有5个节点:

  • 下拉选择框 colorSelect
  • 文本输入框 numberInput
  • 展示颜色信息 colorInfo
  • 展示购买数量信息 numberInfo
  • 决定下一步操作的按钮 nextBtn

开始编码

HTML代码:

选择颜色:
<select id="colorSelect">
  <option value="">请选择</option>
  <option value="red">红色</option>
  <option value="blue">蓝色</option>
</select>
输入购买数量: <input type="text" id="numberInput" />
您选择了颜色: <div id="colorInfo"></div>
您输入了数量: <div id="numberInfo"></div>
<button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>

接下来将分别监听 colorSelectonchange 事件函数和 numberInputoninput 事件函数,然后在这两个事件中作出相应处理。

var colorSelect = document.getElementById('colorSelect'),
  numberInput = document.getElementById('numberInput'),
  colorInfo = document.getElementById('colorInfo'),
  numberInfo = document.getElementById('numberInfo'),
  nextBtn = document.getElementById('nextBtn');

var goods = { // 手机库存 
  "red": 3,
  "blue": 6
};
colorSelect.onchange = function () {
  var color = this.value, // 颜色
    number = numberInput.value,
    stock = goods[color]; // 该颜色手机对应的当前库存
  colorInfo.innerHTML = color;
  if (!color) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if (((number - 0) | 0) !== number - 0) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  // 用户输入的购买数量是否为正整数
  if (number > stock) { // 当前选择数量没有超过库存量 
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
};

对象之间的联系

考虑一下,当触发了 colorSelectonchange 之后,会发生什么事情。首先我们要让 colorInfo 中显示当前选中的颜色,然后获取用户当前输入的购买数量,对用户的输入值进行一些合法性判断。再根据库存数量来判断 nextBtn 的显示状态。

numberInput.oninput = function () {
  var color = colorSelect.value, // 颜色
    number = this.value, // 数量
    stock = goods[color]; // 该颜色手机对应的当前库存
  numberInfo.innerHTML = number;
  if (!color) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if (((number - 0) | 0) !== number - 0) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if (number > stock) { // 当前选择数量没有超过库存量 
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
};

可能遇到的困难

虽然目前顺利完成了代码编写,但随之而来的需求改变有可能给我们带来麻烦。假设现在要求去掉 colorInfonumberInfo 这两个展示区域,我们就要分别改动 colorSelect.onchangenumberInput.onput 里面的代码,因为在先前的代码中,这些对象确实是耦合在一起的。

那么现在,我们页面中需要增加另一个下拉选择框,代表选择手机内存,我们需要计算颜色、内存和购买数量来判断 nextBtn 是显示库存不足还是放入购物车。

首先要增加两个 HTML 节点:

选择内存:
<select id="memorySelect">
  <option value="">请选择</option>
  <option value="32G">32G</option>
  <option value="16G">16G</option>
</select>
您选择了内存: <div id="memoryInfo"></div>
<script>
memorySelect = document.getElementById('memorySelect'),
memoryInfo = document.getElementById('memoryInfo')
</script>

接下来修改表示存库的 JSON 对象以及修改 colorSelectonchange 事件函数:

var goods = { // 手机库存
  "red|32G": 3, // 红色 32G,库存数量为 3 
  "red|16G": 0,
  "blue|32G": 1,
  "blue|16G": 6
};
colorSelect.onchange = function () {
/// 除上述代码外,还有以下判断
  var color = this.value, // 颜色
    number = numberInput.value,
    stock = goods[color + '|' + memory]; // 该颜色手机对应的当前库存

  if (!memory) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
    return;
  }
}

同样要改些 numberInput 事件的相关代码。

最后还要新增 memorySelectonchange 事件函数:

memorySelect.onchange = function () {
  var color = colorSelect.value,
    number = numberInput.value,
    memory = this.value,
    stock = goods[color + '|' + memory];
  if (!color) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if (!memory) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
    return;
  }
  if (((number - 0) | 0) !== number - 0) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if (number > stock) { // 当前选择数量没有超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}

我们可以看到,仅仅增加了一个内存的选择条件,就需要修改如此多的代码,这是因为在目前的实现中,每个节点对象都是耦合在一起的,改变或者增加任何一个节点对象,都要通知到与其相关的对象。

引入中介者

现在引入中介者对象,所有的节点对象只跟中介者通信。当下拉选择框 colorSelectmemorySelect 和文本输入框 numberInput 发生了事件行为时,它们仅仅通知中介者它们被改变了,同时把自身当作参数传入中介者,以便中介者辨别是谁发生了改变。剩下的所有事情都交给中介者对象来完成。

var goods = { // 手机库存 
  "red|32G": 3,
  "red|16G": 0,
  "blue|32G": 1,
  "blue|16G": 6
};
var mediator = (function () {
  var colorSelect = document.getElementById('colorSelect'),
    memorySelect = document.getElementById('memorySelect'),
    numberInput = document.getElementById('numberInput'),
    colorInfo = document.getElementById('colorInfo'),
    memoryInfo = document.getElementById('memoryInfo'),
    numberInfo = document.getElementById('numberInfo'),
    nextBtn = document.getElementById('nextBtn');
  return {
    changed: function (obj) {
      var color = colorSelect.value, // 颜色 
        memory = memorySelect.value,// 内存 
        number = numberInput.value, // 数量 
        stock = goods[color + '|' + memory];  // 颜色和内存对应的手机库存数量
      if (obj === colorSelect) { // 如果改变的是选择颜色下拉框 
        colorInfo.innerHTML = color;
      } else if (obj === memorySelect) {
        memoryInfo.innerHTML = memory;
      } else if (obj === numberInput) {
        numberInfo.innerHTML = number;
      }
      if (!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择手机颜色';
        return;
      }
      if (!memory) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择内存大小';
        return;
      }
      if (((number - 0) | 0) !== number - 0) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请输入正确的购买数量';
        return;
      }
      nextBtn.disabled = false;
      nextBtn.innerHTML = '放入购物车';
    }
  }
})();
// 事件函数:
colorSelect.onchange = function () {
  mediator.changed(this);
};
memorySelect.onchange = function () {
  mediator.changed(this);
};
numberInput.oninput = function () {
  mediator.changed(this);
};

可以想象,某天我们又要新增一些跟需求相关的节点,比如 CPU 型号,那我们只需要稍稍改动 mediator 对象即可:

var goods = { // 手机库存
  "red|32G|800": 3, // 颜色 red,内存 32G,cpu800,对应库存数量为 3
  "red|16G|801": 0,
  "blue|32G|800": 1,
  "blue|16G|801": 6
};
var mediator = (function () {
  var cpuSelect = document.getElementById('cpuSelect');
  return {
    change: function (obj) {
      // 略
      var cpu = cpuSelect.value,
        stock = goods[color + '|' + memory + '|' + cpu];
    }
  }
  // 略
  if (obj === cpuSelect) {
    cpuInfo.innerHTML = cpu;
  }
})();

中介者模式的优缺点

中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。

但是,中介者模式也存在一些缺点。其中,最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。

同系列文章

  1. JavaScript 设计模式之单例模式
  2. JavaScript 设计模式之策略模式
  3. JavaScript 设计模式之代理模式
  4. JavaScript 设计模式之迭代器模式
  5. JavaScript 设计模式之发布-订阅模式
  6. JavaScript 设计模式之命令模式
  7. JavaScript 设计模式之组合模式
  8. JavaScript 设计模式之模板方法模式
  9. JavaScript 设计模式之享元模式
  10. JavaScript 设计模式之职责链模式
  11. JavaScript 设计模式之中介者模式
  12. JavaScript 设计模式之装饰者模式
  13. JavaScript 设计模式之状态模式
  14. JavaScript 设计模式之适配器模式