11_中介者模式
1 简介
作用:解除对象与对象之间的紧耦合关系
增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
- 中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。
- 中介者模式使网状的多对多关系变成了相对简单的一对多关系。
2 现实中的中介者
- 博彩公司
在世界杯期间购买足球彩票,如果没有博彩公司作为中介,上千万的人一起计算赔率和输赢绝对是不可能实现的事情。有了博彩公司作为中介,每个人只需和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就交给博彩公司。
3 泡泡堂游戏
3.1 2个玩家
class Player {
constructor(name) {
this.name = name;
this.enemy = null;
}
win() {
console.log(this.name + ' won');
}
lose() {
console.log(this.name + ' lost');
}
die() {
this.lose()
this.enemy.win()
}
}
let play1 = new Player('皮蛋');
let play2 = new Player('瘦肉');
play1.enemy = play2;
play2.enemy = play1;
play1.die()
3.2 为游戏增加队伍
1. player保存所有玩家
在创建玩家之后,循环 players
来给每个玩家设置队友和敌人
let players = [];
2. 改写Player类
给每个玩家增加属性:队友列表、敌人列表、玩家当前状态、角色名字、玩家所在的队伍颜色
constructor(name, teamColor) {
this.partners = []; // 队友列表
this.enemies = []; // 敌人列表
this.state = 'live'; // 玩家状态
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
}
3. 死亡方法
每个玩家死亡时都遍历其队友的生存状况,若队友全部死亡则游戏失败,同时敌人队伍所有玩家都取得胜利
die() {
let all_dead = true;
this.state = 'dead'; // 设置玩家状态为死亡
for(let partner of partners) {
if(partner.state !== 'dead') {
all_dead = false;
break;
}
}
if(all_dead) { // 如果队友全部死亡
this.lose();
for(let partner of partners) {
partner.lose();
}
for(let enemy of enemies) {
enemy.win()
}
}
}
4. 定义一个工厂来创建玩家
function playerFactory(name, teamColor) {
let newPlayer = new Player(name, teamColor);
for(let player in players) {
if(player.teamColor === newPlayer.teamColor) {
player.partners.push(newPlayer);
newPlayer.partners.push(player);
} else {
player.enemies.push(newPlayer);
newPlayer.enemies.push(player);
}
}
players.push(newPlayer);
return newPlayers;
}
其他情况处理:玩家掉线,解除队伍,玩家改变阵营
3.3 用中介者模式改造
player
对象不再负责具体的执行逻辑,而是将操作转交给中介者对象
class Player {
constructor(name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = 'alive'
}
win() {
console.log(this.name + ' win');
}
lost() {
console.log(this.name + 'lost');
}
die() {
this.state = 'die';
// 给中介者发送消息,玩家死亡
playerDirector.receiveMessage('playerDead', this);
}
remove() {
// 给中介者发送消息,移除一个玩家
playerDirector.receiveMessage('removePlayer', this);
}
changeTeam(color) {
// 给中介者发送消息,玩家换队
playerDirector.receiveMessage('changeTeam', this, color);
}
}
改造工厂函数
工厂函数不再需要给创建的玩家对象设置队友和敌人
function playerFactory(name, teamColor) {
// 创造一个新的玩家对象
let newPlayer = new Player(name, teamColor);
// 给中介者发送消息,新增玩家
playersDirector.receiveMessage('addPlsyer', newPlayer);
return newPlayers;
}
3.4 实现中介者 playerDirector
对象
实现方式
- 方法一:利用
发布-订阅模式
:playerDirector
作为订阅者,各player
作为发布者 - 方法二:在
playerDirector
中开放一些接受消息的接口,各player
可以直接调用该接口来给playerDirector
发送消息
3.5 方法二实现中介者 playerDirector
对象
1. 定义 Operation
: 封装具体操作方法
class Operation {
constructor() {
this.players = {};
}
addPlayer(player) {
let teamColor = player.teamColor;
// 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
this.players[teamColor] = this.players[teamColor] || [];
this.players[teamColor].push(player);
}
removePlayer(player) {
let teamColor = player.teamColor;
let teamPlayers = this.players[teamColor] || [];
for(let i = teamPlayers.length-1; i >= 0; i--) {
if(teamPlayers[i] === player) {
teamPlayers.splice(i, 1)
}
}
}
changeTeam(player, newTeamColor) {
this.removePlayer(player);
player.teamColor = newTeamColor;
this.addPlayer(player);
}
playerDead(player) {
let teamColor = player.teamColor;
// 玩家所在队伍
let teamPlayers = this.players[teamColor];
let all_dead = true;
for(let player of teamPlayers) {
if(player.state !== 'dead') {
all_dead = false; break;
}
}
if(all_dead = true) { // 全部死亡
for(let player of teamPlayers) {
player.lose() // 本队所有玩家 lose
}
for(let color of this.players) {
if(color !== teamColor) {
let teamPlayers = this.players[color];
for(let player of teamPlayers) {
player.win() // 其他队伍所有玩家 win
}
}
}
}
}
}
playerDirector
开放一个对外暴露的接口receiveMessage
负责接收player
对象发送的消息player
对象发送消息时总是把自身this
作为参数发送给playerDirector
以便识别消息来自于哪个玩家
class PlayerDirector {
constructor() {
this.operations = new Operation()
}
receiveMessage() {
// arguments 的第一个参数为消息名称
let message = Array.prototype.shift.call(arguments);
this.operations[message].apply(this, arguments)
}
}
let playerDirector = new PlayerDirector()
除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他的玩家对象
4 购买商品
实现一个手机购买的页面,购买时
- 可以选择手机的颜色以及输入购买数量,同时页面有两个展示区域,分别向用户展示刚刚选择好的颜色和数量
- 还有一个按钮根据当前库存动态显示下一步的操作
4.1 购买手机
1. 接口返回数据
手机库存
let goods = {
red: 3,
blue: 6
}
2. 实现html代码
<div>
选择颜色:<select id="colorSelect">
<option value="">请选择</option>
<option value="red">红色</option>
<option value="blue">蓝色</option>
</select>
</div>
<div>
输入购买数量:<input id="numberInput" type="text" />
</div>
<div>
你选择了颜色:<span id="colorInfo"></span>
</div>
<div>
您输入了数量:<span id="numberInfo"></span>
</div>
<button id="nextBtn" disabled="true">请选择手机和购买数量</button>
3. 将分别监听 colorSelect 的 onchange 事件函数和 numberInput 的 oninput 事件函数
在这两个事件中作出相应处理
let colorSelect = document.getElementById('colorSelect');
let numberInput = document.getElementById('numberInput');
let colorInfo = document.getElementById('colorInfo');
let numberInfo = document.getElementById('numberInfo');
let nextBtn = document.getElementById('nextBtn');
let goods = {
red: 3,
blue: 6
}
colorSelect.onchange = () => {
let color = colorSelect.value;
let number = numberInput.value;
let stock = goods[color];
colorInfo.innerHTML = color;
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
return;
}
// 用户输入的购买数量是否为正整数
if(number <= 0) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
}
// 当前选择数量没有超过库存量
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = '库存不足';
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
numberInput.oninput = () => {
let color = colorSelect.value;
let number = numberInput.value;
let stock = goods[color];
numberInfo.innerHTML = number;
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
return;
}
// 用户输入的购买数量是否为正整数
if(number-0 <= 0) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
}
// 当前选择数量没有超过库存量
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = '库存不足';
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
4.2 可能遇到的问题
- 高耦合
4.3 引入中介者
当下拉选择框和文本输入框发生了行为改变时,仅需通知中介者他们被改变了,同时把自身当做参数传入中介者,以便中介者辨别是谁发生了改变
// 手机库存
let goods = {
'red|32G': 3,
'red|64G': 0,
'blue|32G': 1,
'blue|64G': 6,
}
class Mediator {
constructor(eles) {
Object.assign(this, eles)
console.log(this);
}
changed(obj) {
let color = this.colorSelect.value,
number = this.numberInput.value,
memory = this.memorySelect.value,
stock = goods[color + '|' + memory];
// 如果改变的是选择颜色下拉框
if(obj === this.colorSelect) {
this.colorInfo.innerHTML = color;
} else if(obj === this.numberInput) {
this.numberInfo.innerHTML = number;
} else if(obj === this.memorySelect) {
this.memoryInfo.innerHTML = memory;
}
if(!color) {
this.nextBtn.disabled = true;
this.nextBtn.innerHTML = '请选择手机颜色';
return;
}
if(!memory) {
this.nextBtn.disabled = true;
this.nextBtn.innerHTML = '请选择内存大小';
return;
}
// 输入购买数量是否为正整数
if(number <= 0) {
this.nextBtn.disabled = true;
this.nextBtn.innerHTML = '请输入正确的购买数量';
return;
}
this.nextBtn.disabled = false;
this.nextBtn.innerHTML = '放入购物车';
}
}
let eles = {
colorSelect, memorySelect, numberInput,
colorInfo, memoryInfo, numberInfo,
nextBtn
}
let mediator = new Mediator(eles);
// 事件函数
colorSelect.onchange = function() {
mediator.changed(this)
}
memorySelect.onchange = function() {
mediator.changed(this)
}
numberInput.oninput = function() {
mediator.changed(this)
}
5 总结
- 最少知识原则
- 中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系
缺点
- 系统中会新增一个中介者对象,中介者对象自身往往就是一个难以维护的对象