第十四章 中介者模式
在我们生活的世界中,每个人每个物体之间都会产生一些错综复杂的联系。在应用程序中也是一样,程序由大大小小的单一对象组成,所有这些对象都按照某些关系和规则来通信。
在程序里,也许一个对象会和其他 10 个对象打交道,所以它会保持 10 个对象的引用。当程序的规模越大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用。当我们改变或删除其中一个对象时,很可能需要通知所有引用到它的对象,这样一来,即使很小一点的修改都必须小心翼翼。
如上图所示,当对象 A 发生了改变,则需要同时通知 B、D、E、F 这四个对象。
面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之间的联系激增,又可能反过来降低它们的可复用性。
中介者模式的作用就是解除对象与对象之间的耦合关系。增加一个中介者对象后,所有相关的对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间的耦合松散,而且可以独立改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。
使用中介者模式后,A 发生改变则只需要通知中介者对象就可以了。
14.3 中介者模式的例子——购买商品
假设,我们目前要做一个手机商品页面,通过页面,我们可以选择手机的颜色,可以填写购买数量。当填写了对应内容后,就可以根据库存来提醒用户:
- 当用户选择的颜色的库存小于数量时,提示库存不足
- 当大于数量时,提示加入购物车
下面是 HTML 代码
js1 选择颜色: 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 获取所有对象
js1var 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绑定事件
js1var 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 }
现在交互变成这样
14.3.2 对象之间的联系
现在当我们选择颜色后,输入对应的数量,此时会对购买数量做一些合法判断。此外还需要根据库存来判断 nextBtn 的现实状态。
我们的逻辑此时是耦合的:颜色、数量与下一步按钮的状态形成了耦合。
14.3.3 可能遇到的困难
如果此时需求有所改变,比如我们需要去掉 colorInfo 和 numberInfo 两个区域,我们就需要同步改动numberInput.oninput和colorSelect.onchange内部的代码。
假设此时需求增加了,我们需要新增一个可以选择内存的选择框和显示内存的信息节点
html1选择内存: 2<select id="memorySelect"> 3 <option value="">请选择</option> 4 <option value="32G">32G</option> 5 <option value="16G">16G</option> 6</select>
js1 您选择了内存: 2 <div id="memoryInfo"></div>
我们也要给他绑定事件
js1var 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}