第十四章 中介者模式

在我们生活的世界中,每个人每个物体之间都会产生一些错综复杂的联系。在应用程序中也是一样,程序由大大小小的单一对象组成,所有这些对象都按照某些关系和规则来通信。

在程序里,也许一个对象会和其他 10 个对象打交道,所以它会保持 10 个对象的引用。当程序的规模越大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用。当我们改变或删除其中一个对象时,很可能需要通知所有引用到它的对象,这样一来,即使很小一点的修改都必须小心翼翼。

img

如上图所示,当对象 A 发生了改变,则需要同时通知 B、D、E、F 这四个对象。

面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之间的联系激增,又可能反过来降低它们的可复用性。

中介者模式的作用就是解除对象与对象之间的耦合关系。增加一个中介者对象后,所有相关的对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间的耦合松散,而且可以独立改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

img

使用中介者模式后,A 发生改变则只需要通知中介者对象就可以了。

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

假设,我们目前要做一个手机商品页面,通过页面,我们可以选择手机的颜色,可以填写购买数量。当填写了对应内容后,就可以根据库存来提醒用户:

  • 当用户选择的颜色的库存小于数量时,提示库存不足
  • 当大于数量时,提示加入购物车

下面是 HTML 代码

js
1    选择颜色:
2    <select id="colorSelect">
3      <option value="">请选择</option>
4      <option value="red">红色</option>
5      <option value="blue">蓝色</option>
6    </select>
7
8    输入购买数量:
9    <input type="text" id="numberInput" /> 您选择了颜色:
10    <div id="colorInfo"></div>
11    <br />
12    您输入了数量:
13    <div id="numberInfo"></div>
14    <br />
15    <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>

首先我们通过 DOM 获取所有对象

js
1var colorSelect = document.getElementById('colorSelect'), //颜色选择框
2  numberInput = document.getElementById('numberInput'), //数量输入框
3  colorInfo = document.getElementById('colorInfo'), //颜色信息
4  numberInfo = document.getElementById('numberInfo'), //
5  nextBtn = document.getElementById('nextBtn')

其次要给colorSelect和numberInput绑定事件

js
1var goods = {
2  // 手机库存
3  red: 3,
4  blue: 6
5};
6
7colorSelect.onchange = function () {
8  var color = this.value, // 颜色
9    number = numberInput.value, // 数量
10    stock = goods[color]; // 该颜色手机对应的当前库存
11
12  colorInfo.innerHTML = color;
13  if (!color) {
14    nextBtn.disabled = true;
15    nextBtn.innerHTML = "请选择手机颜色";
16    return;
17  }
18
19  if (Number.isInteger(number - 0) && number > 0) {
20    // 用户输入的购买数量是否为正整数
21    nextBtn.disabled = true;
22    nextBtn.innerHTML = "请输入正确的购买数量";
23    return;
24  }
25
26  if (number > stock) {
27    // 当前选择数量超过库存量
28    nextBtn.disabled = true;
29    nextBtn.innerHTML = "库存不足";
30    return;
31  }
32
33  nextBtn.disabled = false;
34  nextBtn.innerHTML = "放入购物车";
35};
36
37numberInput.oninput = function () {
38  var color = colorSelect.value, // 颜色
39    number = this.value, // 数量
40    stock = goods[color]; // 该颜色手机对应的当前库存
41
42  numberInfo.innerHTML = number;
43
44  if (!color) {
45    nextBtn.disabled = true;
46    nextBtn.innerHTML = "请选择手机颜色";
47    return;
48  }
49
50  if (((number - 0) | 0) !== number - 0) {
51    // 输入购买数量是否为正整数
52    nextBtn.disabled = true;
53    nextBtn.innerHTML = "请输入正确的购买数量";
54    return;
55  }
56  if (number > stock) {
57    // 当前选择数量没有超过库存量
58    nextBtn.disabled = true;
59    nextBtn.innerHTML = "库存不足";
60    return;
61  }

现在交互变成这样

image-20211116221536314

14.3.2 对象之间的联系

现在当我们选择颜色后,输入对应的数量,此时会对购买数量做一些合法判断。此外还需要根据库存来判断 nextBtn 的现实状态。

我们的逻辑此时是耦合的:颜色、数量与下一步按钮的状态形成了耦合。

14.3.3 可能遇到的困难

如果此时需求有所改变,比如我们需要去掉 colorInfo 和 numberInfo 两个区域,我们就需要同步改动numberInput.oninput和colorSelect.onchange内部的代码。

假设此时需求增加了,我们需要新增一个可以选择内存的选择框和显示内存的信息节点

html
1选择内存:
2<select id="memorySelect">
3  <option value="">请选择</option>
4  <option value="32G">32G</option>
5  <option value="16G">16G</option>
6</select>
js
1    您选择了内存:
2    <div id="memoryInfo"></div>

我们也要给他绑定事件

js
1var memoryInfo = document.getElementById('memoryInfo')
2var memorySelect = document.getElementById('memorySelect')
3memorySelect.onchange = function () {
4  var color = colorSelect.value, // 颜色
5    number = numberInput.value, // 数量
6    memory = this.value,
7    stock = goods[color + '|' + memory] // 该颜色手机对应的当前库存
8  memoryInfo.innerHTML = memory
9
10  if (!color) {
11    nextBtn.disabled = true
12    nextBtn.innerHTML = '请选择手机颜色'
13    return
14  }
15  if (!memory) {
16    nextBtn.disabled = true
17    nextBtn.innerHTML = '请选择内存大小'
18    return
19  }
20  if (Number.isInteger(number - 0) && number > 0) {
21    // 输入购买数量是否为正整数
22    nextBtn.disabled = true
23    nextBtn.innerHTML = '请输入正确的购买数量'
24    return
25  }
26  if (number > stock) {
27    // 当前选择数量没有超过库存量
28    nextBtn.disabled = true
29    nextBtn.innerHTML = '库存不足'
30    return
31  }
32
33  nextBtn.disabled = false
34  nextBtn.innerHTML = '放入购物车'
35}