# 中介者模式
一个应用不论大小,都是由一些彼此独立的对象组成的。所有的对象都需要 一个通讯的方式来保持可维护性,即你可以安全地修改应用的一部分而不破 坏其它 部分。随着应用的开发和维护,会有越来越多的对象。然后,在重构 代码的时候,对象可能会被移除或者被重新安排。当对象知道其它对象的太 多信息并且直接通讯 (直接调用彼此的方法或者修改属性)时,会导致我们 不愿意看到的紧耦合。当对象耦合很紧时,要修改一个对象而不影响其它的 对象是很困难的。此时甚至连一个 最简单的修改都变得不那么容易,甚至连 一个修改需要用多长时间都难以评估。
中介者模式就是一个缓解此问题的办法,它通过解耦来提升代码的可维护性 (见图 7-7)。在这个模式中,各个彼此合作的对象并不直接通讯,而是通 过一 个 mediator(中介者)对象通讯。当一个对象改变了状态后,它就通 知中介者,然后中介者再将这个改变告知给其它应该知道这个变化的对象。
# 中介者示例
我们来看一个使用中介者模式的实例。这个应用是一个游戏,它的玩法是比 较两位游戏者在半分钟内按下按键的次数,次数多的获胜。玩家 1 需要按的 是 1,玩家 2 需要按的是 0(这样他们的手指不会搅在一起)。当前分数会显 示在一个计分板上。
对象列表如下:
- Player 1
- Player 2
- Scoreboard
- Mediator
中介者 Mediator 知道所有的对象。它与输入设备(键盘)打交道,处理 keypress 事件,决定现在是哪位玩家玩的,然后通知这个 玩家(见图 7-8)。 玩家负责玩(即给自己的分数加一分),然后通知中介者他这一轮已经玩完。 中介者再告知计分板最新的分数,计分板更新显示。 除了中介者之外,其它的对象都不知道有别的对象存在。这样就使得更新这 个游戏变得很简单,比如要添加一位玩家或者是添加另外一个显示剩余时间 的地方。 你可以在这里看到这个游戏的在线演示 http://jspatterns.com/book/7/mediator.html。
玩家对象是通过 Player()构造函数来创建的,有自己的 points 和 name 属性。 原型上的 play()方法负责给自己加一分然后通知中介者:
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
2
3
4
5
6
7
8
scoreboard 对象(计分板)有一个 update()方法,它会在每次玩家玩完后被 中介者调用。计分析根本不知道玩家的任何信息,也不保存分数,它只负责 显示中介者给过来的分数:
var scoreboard = {
// HTML element to be updated
element: document.getElementById('results'),
// update the score display
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += '<p><strong>' + i + '<\/strong>: ';
msg += score[i];
msg += '<\/p>';
} }
this.element.innerHTML = msg;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现在我们来看一下 mediator 对象(中介者)。在游戏初始化的时候,在 setup() 方法中创建游戏者,然后放后 players 属性以便后续使 用。played()方法会 被游戏者在每轮玩完后调用,它更新 score 哈希然表然后将它传给 scoreboard 用于显示。最后一个方法是 keypress(),负责处理键盘事件, 决定是哪位玩家玩的,并且通知它:
var mediator = {
// all the players
players: {},
// initialization
setup: function () {
var players = this.players;
players.home = new Player('Home');
players.guest = new Player('Guest');
},
// someone plays, update the score
played: function () {
var players = this.players,
score = {
Home: players.home.points,
Guest: players.guest.points
};
scoreboard.update(score);
},
// handle user interactions
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // key "1"
mediator.players.home.play();
return; }
if (e.which === 48) { // key "0"
mediator.players.guest.play();
return; }
} };
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
最后一件事是初始化和结束游戏:
// go!
mediator.setup();
window.onkeypress = mediator.keypress;
// game over in 30 seconds
setTimeout(function () {
window.onkeypress = null;
alert('Game over!');
}, 30000);
2
3
4
5
6
7
8
9