JavaScript设计模式与开发实践(十)中介者模式

中介者模式

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

游戏例子

假如我们实现一个简单的双人游戏类,它有三个方法win,lose,die分别表示胜利,失败和死亡。因为玩家数目是二,所以一方死亡就宣布另一方失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Player {
name = null;
enemy = null;
constructor(name) {
this.name = name
}
win() {
console.log(this.name + ':won');
}
lose() {
console.log(this.name + ':lose');
}
die() {
this.lose()
this.enemy.win()
}
}
const player1 = new Player('A');
const player2 = new Player('B');

player1.enemy = player2;
player2.enemy = player1;

player1.die();

升级一下

现在我们改进一下游戏,将玩家数量变多,并分成红蓝两队进行游戏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class Player {
name = null;
teamColor = null;
partners = [];
enemys = [];
state = 'live';
constructor(name, teamColor) {
this.name = name
this.teamColor = teamColor
}
win() {
console.log(this.name + ':winner');
}
lose() {
console.log(this.name + ':loser');
}
die() {
let all_dead = true
this.state = 'dead'
this.partners.forEach(item => {
if (item.state !== 'dead') {
all_dead = false
return
}
})
if (all_dead === true) { // 如果队友全部死亡
this.lose(); // 通知自己游戏失败
this.partners.forEach(item => {
item.lose()
})
this.enemys.forEach(item => {
item.win()
})
}
}
}

playerFactory.players = []
function playerFactory(name, teamColor) {
const newPlayer = new Player(name, teamColor)
playerFactory.players.forEach(player => {
if (player.teamColor === newPlayer.teamColor) { // 如果是同一队的玩家
player.partners.push(newPlayer); // 相互添加到队友列表
newPlayer.partners.push(player);
} else {
player.enemies.push(newPlayer); // 相互添加到敌人列表
newPlayer.enemies.push(player);
}
})
return newPlayer
}

//红队:
const player1 = playerFactory('A', 'red'),
player2 = playerFactory('B', 'red'),
player3 = playerFactory('C', 'red'),
player4 = playerFactory('D', 'red');
//蓝队:
const player5 = playerFactory('E', 'blue'),
player6 = playerFactory('F', 'blue'),
player7 = playerFactory('G', 'blue'),
player8 = playerFactory('I', 'blue');

player1.die();
player2.die();
player4.die();
player3.die();

引入中介者

这个Player类的缺点是,每个成员都需要维护己方和敌人的数组,也就是需要维护N的平方数目。

而如果人数是几百人,如果有一个玩家掉线,必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家。游戏也许还有解除队伍和添加到别的队伍的功能,红色玩家可以突然变成蓝色玩家,在目前的代码架构下就显得非常笨重。

中介者(playerDirector)的实现一般有两种:

  1. 发布订阅模式,将中介者实现为发布者,其他玩家为订阅者,一旦player的状态发生改变,便推送消息给 playerDirector,playerDirector 处理消息后将反馈发送给其他 player。
  2. 在playerDirector中开放一些接收消息的接口,各player可以直接调用该接口来给playerDirector发送消息,player只需传递一个参数给playerDirector,这个参数的目的是使playerDirector可以识别发送者。同样,playerDirector接收到消息之后会将处理结果反馈给其他player。
  3. 这里选择第二种方式,这样每个player只需要调用playerDirector对应的消息,而不需要知道自己和其他player之间的关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class Player {
state = 'alive'
constructor(name, teamColor) {
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
}
win() {
console.log(this.name + ' won ');
}
lose() {
console.log(this.name + ' lose ');
}
die() {
this.state = 'dead'
PlayerDirector.reciveMessage('playerDead', this);
}
remove() {
PlayerDirector.reciveMessage('removePlayer', this); // 给中介者发送消息,移除一个玩家
}
changeTeam(color) {
PlayerDirector.reciveMessage('changeTeam', this, color); // 给中介者发送消息,玩家换队
}
}
function playerFactory(name, teamColor) {
let newPlayer = new Player(name, teamColor); // 创造一个新的玩家对象
PlayerDirector.reciveMessage('addPlayer', newPlayer); // 给中介者发送消息,新增玩家
return newPlayer;
};

class PlayerDirector {
static players = {};// 保存所有玩家
static addPlayer(player) {
const teamColor = player.teamColor; // 玩家的队伍颜色
this.players[teamColor] = this.players[teamColor] || []; // 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
this.players[teamColor].push(player); // 添加玩家进队伍
};
static removePlayer(player) {
let teamColor = player.teamColor, // 玩家的队伍颜色
teamPlayers = this.players[teamColor] || []; // 该队伍所有成员
for (let i = teamPlayers.length - 1; i >= 0; i--) { // 遍历删除
if (teamPlayers[i] === player) {
teamPlayers.splice(i, 1);
}
}
};
static changeTeam(player, newTeamColor) {
this.removePlayer(player); // 从原队伍中删除
player.teamColor = newTeamColor; // 改变队伍颜色
this.addPlayer(player); // 增加到新队伍中
};
static playerDead(player) { // 玩家死亡
let teamColor = player.teamColor,
teamPlayers = this.players[teamColor]; // 玩家所在队伍
let all_dead = true;
for (let i = 0, player; player = teamPlayers[i++];) {
if (player.state !== 'dead') {
all_dead = false;
break;
}
}
if (all_dead === true) { // 全部死亡
for (let i = 0, player; player = teamPlayers[i++];) {
player.lose(); // 本队所有玩家 lose
}
for (let color in this.players) {
if (color !== teamColor) {
let teamPlayers = this.players[color]; // 其他队伍的玩家
for (let i = 0, player; player = teamPlayers[i++];) {
player.win(); // 其他队伍所有玩家 win
}
}
}
}
};
static reciveMessage() {
let message = Array.prototype.shift.call(arguments); // arguments 的第一个参数为消息名称
this[message].apply(this, arguments);
};
}

const player1 = playerFactory('A', 'red'),
player2 = playerFactory('B', 'red'),
player3 = playerFactory('C', 'red'),
player4 = playerFactory('D', 'red');
//蓝队:
const player5 = playerFactory('E', 'blue'),
player6 = playerFactory('F', 'blue'),
player7 = playerFactory('G', 'blue'),
player8 = playerFactory('I', 'blue');

player1.die();
player2.die();
player4.die();
player3.die();

总结

  • 中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象 之间的网状多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者 对象来实现和维护。
  • 不过,中介者模式也存在一些缺点。其中,最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。

JavaScript设计模式与开发实践(十)中介者模式
https://jing-jiu.github.io/jing-jiu/2023/01/15/notebooks/JavaScript设计模式与实践/设计模式(十)/
作者
Jing-Jiu
发布于
2023年1月15日
许可协议