目录

  1. 介绍
  2. 表单数据同步实例
  3. 玩家对战实例

    1. 初始代码
    2. 改造代码

介绍

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

中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互,中介者模式使网状的多对多关系变成了相对简单的一对多关系。

表单数据同步实例

接下来我们将使用中介者模式实现一个表单中的两个文本框之间的数据同步,代码如下:

  1. <form name="form">
  2. <label for="phone1">电话号码1:</label>
  3. <input name="phone1" type="text" />
  4. <label for="phone2">电话号码2:</label>
  5. <input name="phone2" type="text" />
  6. </form>
  7. <script>
  8. var Listener = function (formName) {
  9. this.form = document.forms[formName];
  10. this.fieldEls = [];
  11. };
  12. Listener.prototype.register = function (field) {
  13. var self = this;
  14. this.fieldEls.push(form[field]);
  15. (function(field) {
  16. self.form[field].addEventListener('keyup', function(e) {
  17. for (var i = 0; i < self.fieldEls.length; i++) {
  18. if (self.fieldEls[i].name !== field) {
  19. self.fieldEls[i].value = e.target.value;
  20. }
  21. }
  22. });
  23. })(field);
  24. }
  25. var listener = new Listener('form');
  26. listener.register('phone1');
  27. listener.register('phone2');
  28. </script>

以上代码的中介者就是由构造函数 Listener 生成的 listener 实例,我们调用 listener 的register()方法分别注册两个表单元素(phone1 和 phone2),这样就实现了两个表单元素输入内容的同步化。register()内部的具体代码实现,如果感兴趣可以深入研究下。

如果不使用中介者模式,要实现表单元素间的数据同步,我们可能要给每个表单元素绑定输入内容变化侦听句柄,并且要将每个表单元素加入到一个”公共组织”,当一个表单元素的值发生改变时,在侦听句柄中进行”公共组织”所有成员的数据同步操作,这其实就是耦合,所以在这个场景下使用中介者模式恰到好处。

玩家对战实例

接下来的玩家对战实例的实现原理实际上和表单数据同步的原理差不多。我们将分别展示不使用中介者模式和使用中介者模式的代码实现。

初始代码

没有使用中介者模式的代码:

  1. // 玩家列表
  2. var players = [];
  3. // 玩家类
  4. function Player(name, teamColor) {
  5. this.partners = []; // 队友列表
  6. this.enemies = []; // 敌人列表
  7. this.name = name; // 角色名字
  8. this.state = 'live'; // 玩家状态
  9. this.teamColor = teamColor; // 队伍颜色
  10. };
  11. Player.prototype.win = function () {
  12. console.log('winner: ' + this.name);
  13. };
  14. Player.prototype.lose = function () {
  15. console.log('loser: ' + this.name);
  16. };
  17. Player.prototype.die = function () {
  18. var all_dead = true;
  19. this.state = 'dead'; // 玩家状态设为死亡
  20. // 遍历队友列表
  21. for (var i = 0, l = this.partners.length; i < l; i++) {
  22. // 如果还有一个队友没有死亡,则游戏还未结束
  23. if (this.partners[i].state !== 'dead') {
  24. all_dead = false;
  25. break;
  26. }
  27. }
  28. if (all_dead) {
  29. this.lose();
  30. // 通知所有队友游戏失败
  31. for (var i = 0, l = this.partners.length; i < l; i++) {
  32. this.partners[i].lose();
  33. }
  34. // 通知所有敌人游戏胜利
  35. for (var i = 0, l = this.ememies.length; i < l; i++){
  36. this.ememies[i].win();
  37. }
  38. }
  39. };
  40. // 定义一个工厂来创建玩家
  41. var playerFactory = function (name, teamColor) {
  42. var newPlayer = new Player(name, teamColor); // 创建新玩家
  43. // 通知所有玩家,有新玩家加入游戏
  44. for (var i = 0, l = players.length; i < l; i++) {
  45. if (players[i].teamColor === newPlayer.teamColor) {
  46. // 相互添加到队友列表
  47. players[i].partners.push(newPlayer);
  48. newPlayer.partners.push(players[i]);
  49. } else {
  50. // 相互添加到敌人列表
  51. players[i].enemies.push(newPlayer);
  52. newPlayer.enemies.push(players[i]);
  53. }
  54. }
  55. players.push(newPlayer);
  56. return newPlayer;
  57. }

从以上代码可以看出,玩家与队友,玩家与敌人存在耦合关系,每次增加一个新玩家,就要对所有玩家的队友列表或敌人列表进行更新,如果玩家数量越来越多,这种维护成本将会变的非常大。这个时候仔细分析一下,其实每个玩家都不需要牢牢记住自己的队友和敌人是哪些人,我们可以增加一个”系统”(中介者)来管理玩家之间的关系,当游戏结束时,也由这个”系统”对所有玩家发出通知,这样子就可以减少大量的遍历操作,玩家间的耦合关系也将变得松散。要达到这种目的,就要使用中介者模式来改造代码。

改造代码

首先简化玩家类:

  1. function Player(name, teamColor) {
  2. this.name = name; // 角色名字
  3. this.teamColor = teamColor; // 队伍颜色
  4. this.state = 'alive'; // 玩家生存状态
  5. };
  6. Player.prototype.win = function () {
  7. console.log('winner: ' + this.name);
  8. };
  9. Player.prototype.lose = function () {
  10. console.log('loser: ' + this.name);
  11. };
  12. Player.prototype.die = function () {
  13. this.state = 'dead';
  14. // 给中介者发送消息,玩家死亡
  15. playerDirector.ReceiveMessage('playerDead', this);
  16. };
  17. Player.prototype.remove = function () {
  18. // 给中介者发送消息,移除一个玩家
  19. playerDirector.ReceiveMessage('removePlayer', this);
  20. };

接下来要考虑如何设计中介者对象 playerDirector,其实现有两种方法:

  1. 利用发布-订阅模式。将 playerDirector 实现为订阅者,各 player 作为发布者,一旦 player 的状态发生变化,便推送消息给 playerDirector,playerDirector 处理消息后将反馈发送给其他 player;

  2. 在 playerDirector 中开放一些接收消息的接口,各 player 可以直接调用该接口来给 playerDirector 发送消息,player 只需传递一个参数给 playerDirector,这个参数的目的是使 playerDirector 可以识别发送者。同样,playerDirector 接收到消息后会将处理结果反馈给其他 player。

以第二种方式创建中介者对象为例:

  1. // 创建中介者对象
  2. var playerDirector = (function () {
  3. var players = {}; // 保存所有玩家
  4. var operations = {}; // 中介者可以执行的操作
  5. // 新增一个玩家
  6. operations.addPlayer = function (player) {
  7. var teamColor = player.teamColor;
  8. players[teamColor] = players[teamColor] || [];
  9. players[teamColor].push(player);
  10. };
  11. // 移除一个玩家
  12. operations.removePlayer = function (player) {
  13. var teamColor = player.teamColor;
  14. var teamPlayers = players[teamColor] || [];
  15. for (var i = teamPlayers.length - 1; i >= 0; i--) {
  16. if (teamPlayers[i] === player) {
  17. teamPlayers.splice(i, 1);
  18. }
  19. }
  20. };
  21. // 玩家角色死亡
  22. operations.playerDead = function (player) {
  23. var teamColor = player.teamColor;
  24. var teamPlayers = players[teamColor];
  25. var all_dead = true;
  26. // 遍历队友列表
  27. for (var i = 0, l = teamPlayers.length; i < l; i++) {
  28. // 如果还有一个队友没有死亡,则游戏还未结束
  29. if (teamPlayers[i].state !== 'dead') {
  30. all_dead = false;
  31. break;
  32. }
  33. }
  34. if (all_dead) {
  35. // 通知所有队友游戏失败
  36. for (var i = 0, l = teamPlayers.length; i < l; i++) {
  37. teamPlayers[i].lose();
  38. }
  39. for (var color in players) {
  40. // 通知所有敌人游戏胜利
  41. if (color !== teamColor) {
  42. var teamPlayers = players[color];
  43. for (var i = 0, l = teamPlayers.length; i < l; i++) {
  44. teamPlayers[i].win();
  45. }
  46. }
  47. }
  48. }
  49. };
  50. var ReceiveMessage = function () {
  51. // arguments的第一个参数为消息名称
  52. var message = Array.prototype.shift.call(arguments);
  53. operations[message].apply(this, arguments);
  54. };
  55. return {
  56. ReceiveMessage: ReceiveMessage
  57. }
  58. })();

  1. ID : 60
  2. DATE : 2018/01/14
  3. AUTHER : WJT20
  4. TAG : JavaScript