JavaScript设计模式之中介者模式实例分析
今天小编给大家分享一下JavaScript设计模式之中介者模式实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
中介者模式
在我们生活的世界中,每个人每个物体之间都会产生一些错综复杂的联系。在应用程序里也是一样,程序由大大小小的单一对象组成,所有这些对象都按照某种关系和规则来通信。
平时我们大概能记住 10 个朋友的电话、30 家餐馆的位置。在程序里,也许一个对象会和其他 10 个对象打交道,所以它会保持 10 个对象的引用。当程序的规模增大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用。当我们改变或删除其中一个对象的时候,很可能需要通知所有引用到它的对象。这样一来,就像在心脏旁边拆掉一根毛细血管一般, 即使一点很小的修改也必须小心翼翼,如下图所示。
面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之间的联系激增,又有可能会反过来降低它们的可复用性。
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系,如下图所示。
在前面的图中,如果对象 A 发生了改变,则需要同时通知跟 A 发生引用关系的 B、D、E、F 这 4 个对象;而在上图中,使用中介者模式改进之后,A 发生改变时则只需要通知这个中介者对象即可。
现实中的中介者
在现实生活中也有很多中介者的例子,例如机场指挥塔。
中介者也被称为调停者,我们想象一下机场的指挥塔,如果没有指挥塔的存在,每一架飞机要和方圆 100 公里内的所有飞机通信,才能确定航线以及飞行状况,后果是不可想象的。现实中的情况是,每架飞机都只需要和指挥塔通信。指挥塔作为调停者,知道每一架飞机的飞行状况,所以它可以安排所有飞机的起降时间,及时做出航线调整。
下面我们来看中介者模式在下面这个案例中的应用。
中介者模式的例子
泡泡堂游戏
大家可能都还记得泡泡堂游戏,现在我们来一起回顾这个游戏,假设在游戏之初只支持两个玩家同时进行对战。
先定义一个玩家构造函数,它有 3 个简单的原型方法:Play.prototype.win
、Play.prototype.lose
以及表示玩家死亡的 Play.prototype.die
。
因为玩家的数目是 2,所以当其中一个玩家死亡的时候游戏便结束, 同时通知它的对手胜利。 这段代码看起来很简单:
function Player(name) { this.name = name this.enemy = null; // 敌人 }; Player.prototype.win = function () { console.log(this.name + ' won '); }; Player.prototype.lose = function () { console.log(this.name + ' lost'); }; Player.prototype.die = function () { this.lose(); this.enemy.win(); };
接下来创建 2 个玩家对象:
const player1 = new Player('玩家一'); const player2 = new Player('玩家二');
给玩家相互设置敌人:
player1.enemy = player2; player2.enemy = player1;
当玩家 player1
被泡泡炸死的时候,只需要调用这一句代码便完成了一局游戏:
player1.die();// 输出:玩家一 lost、玩家二 won
然而真正的泡泡堂游戏至多可以有 8 个玩家,并分成红蓝两队进行游戏。
为游戏增加队伍
现在我们改进一下游戏。因为玩家数量变多,用下面的方式来设置队友和敌人无疑很低效:
player1.partners = [player1, player2, player3, player4]; player1.enemies = [player5, player6, player7, player8]; Player5.partners = [player5, player6, player7, player8]; Player5.enemies = [player1, player2, player3, player4];
所以我们定义一个数组 players
来保存所有的玩家,在创建玩家之后,循环 players 来给每个玩家设置队友和敌人:
const players = [];
再改写构造函数 Player
,使每个玩家对象都增加一些属性,分别是队友列表、敌人列表 、 玩家当前状态、角色名字以及玩家所在的队伍颜色:
function Player(name, teamColor) { this.partners = []; // 队友列表 this.enemies = []; // 敌人列表 this.state = 'live'; // 玩家状态 this.name = name; // 角色名字 this.teamColor = teamColor; // 队伍颜色 };
玩家胜利和失败之后的展现依然很简单,只是在每个玩家的屏幕上简单地弹出提示:
Player.prototype.win = function () { // 玩家团队胜利 console.log('winner: ' + this.name); }; Player.prototype.lose = function () { // 玩家团队失败 console.log('loser: ' + this.name); };
玩家死亡的方法要变得稍微复杂一点,我们需要在每个玩家死亡的时候,都遍历其他队友的生存状况,如果队友全部死亡,则这局游戏失败,同时敌人队伍的所有玩家都取得胜利,代码如下:
Player.prototype.die = function () { // 玩家死亡 let all_dead = true; this.state = 'dead'; // 设置玩家状态为死亡 for (let i = 0; i < this.partners.length; i++) { // 遍历队友列表 if (this.partners[i].state !== 'dead') { // 如果还有一个队友没有死亡,则游戏还未失败 all_dead = false; break; } } if (all_dead === true) { // 如果队友全部死亡 this.lose(); // 通知自己游戏失败 for (let i = 0; i < this.partners.length; i++) { // 通知所有队友玩家游戏失败 this.partners[i].lose(); } for (let i = 0; i < this.enemies.length; i++) { // 通知所有敌人游戏胜利 this.enemies[i].win(); } } };
最后定义一个工厂来创建玩家:
const playerFactory = function (name, teamColor) { const newPlayer = new Player(name, teamColor); // 创建新玩家 for (let i = 0; i < players.length; i++) { // 通知所有的玩家,有新角色加入 if (players[i].teamColor === newPlayer.teamColor) { // 如果是同一队的玩家 players[i].partners.push(newPlayer); // 相互添加到队友列表 newPlayer.partners.push(players[i]); } else { players[i].enemies.push(newPlayer); // 相互添加到敌人列表 newPlayer.enemies.push(players[i]); } } players.push(newPlayer); return newPlayer; };
现在来感受一下, 用这段代码创建 8 个玩家:
//红队: var player1 = playerFactory('皮蛋', 'red'), player2 = playerFactory('小乖', 'red'), player3 = playerFactory('宝宝', 'red'), player4 = playerFactory('小强', 'red'); //蓝队: var player5 = playerFactory('黑妞', 'blue'), player6 = playerFactory('葱头', 'blue'), player7 = playerFactory('胖墩', 'blue'), player8 = playerFactory('海盗', 'blue');
让红队玩家全部死亡:
player1.die(); player2.die(); player4.die(); player3.die();
结果如下:
loser: 宝宝
loser: 皮蛋
loser: 小乖
loser: 小强
winner: 黑妞
winner: 葱头
winner: 胖墩
winner: 海盗
玩家增多带来的困扰
现在我们已经可以随意地为游戏增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧紧耦合在一起的。在此段代码中,每个玩家对象都有两个属性,this.partners
和 this.enemies
,用来保存其他玩家对象的引用。当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡时,都必须要显式地遍历通知其他对象。
在这个例子中只创建了 8 个玩家,或许还没有对你产生足够多的困扰,而如果在一个大型网络游戏中,画面里有成百上千个玩家,几十支队伍在互相厮杀。如果有一个玩家掉线,必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家。游戏也许还有解除队伍和添加到别的队伍的功能,红色玩家可以突然变成蓝色玩家,这就不再仅仅是循环能够解决的问题了。面对这样的需求,我们上面的代码可以迅速进入投降模式。
用中介者模式改造泡泡堂游戏
现在我们开始用中介者模式来改造上面的泡泡堂游戏, 改造后的玩家对象和中介者的关系如下图所示。
首先仍然是定义 Player
构造函数和 player
对象的原型方法,在 player
对象的这些原型方法 中,不再负责具体的执行逻辑,而是把操作转交给中介者对象,我们把中介者对象命名为 playerDirector
:
function Player(name, teamColor) { this.name = name; // 角色名字 this.teamColor = teamColor; // 队伍颜色 this.state = 'alive'; // 玩家生存状态 }; Player.prototype.win = function () { console.log(this.name + ' won '); }; Player.prototype.lose = function () { console.log(this.name + ' lost'); }; /*******************玩家死亡*****************/ Player.prototype.die = function () { this.state = 'dead'; playerDirector.reciveMessage('playerDead', this); // 给中介者发送消息,玩家死亡 }; /*******************移除玩家*****************/ Player.prototype.remove = function () { playerDirector.reciveMessage('removePlayer', this); // 给中介者发送消息,移除一个玩家 }; /*******************玩家换队*****************/ Player.prototype.changeTeam = function (color) { playerDirector.reciveMessage('changeTeam', this, color); // 给中介者发送消息,玩家换队 };
再继续改写之前创建玩家对象的工厂函数,可以看到,因为工厂函数里不再需要给创建的玩家对象设置队友和敌人,这个工厂函数几乎失去了工厂的意义:
const playerFactory = function (name, teamColor) { const newPlayer = new Player(name, teamColor); // 创造一个新的玩家对象 playerDirector.reciveMessage('addPlayer', newPlayer); // 给中介者发送消息,新增玩家 return newPlayer; };
最后,我们需要实现这个中介者 playerDirector
对象,一般有以下两种方式。
利用发布—订阅模式。将
playerDirector
实现为订阅者,各player
作为发布者,一旦player
的状态发生改变,便推送消息给playerDirector
,playerDirector
处理消息后将反馈发送 给其他player
。在
playerDirector
中开放一些接收消息的接口,各player
可以直接调用该接口来给playerDirector
发送消息,player
只需传递一个参数给playerDirector
,这个参数的目的是使playerDirector
可以识别发送者。同样,playerDirector
接收到消息之后会将处理结果反馈给其他player
。
这两种方式的实现没什么本质上的区别。在这里我们使用第二种方式,playerDirector
开放一个对外暴露的接口 reciveMessage
,负责接收 player
对象发送的消息,而 player
对象发送消息的时候,总是把自身 this
作为参数发送给 playerDirector
,以便 playerDirector
识别消息来自于哪个玩家对象,代码如下:
const playerDirector = (function () { const players = {}, // 保存所有玩家 operations = {}; // 中介者可以执行的操作 /** * 新增一个玩家 * @param {Player} player 玩家 */ operations.addPlayer = function (player) { const teamColor = player.teamColor; // 玩家的队伍颜色 // 如果该颜色的玩家还没有成立队伍,则新成立一个队伍 players[teamColor] = players[teamColor] || []; players[teamColor].push(player); // 添加玩家进队伍 }; /** * 移除一个玩家 * @param {Player} player 玩家 */ operations.removePlayer = function (player) { const teamColor = player.teamColor, // 玩家的队伍颜色 teamPlayers = players[teamColor] || []; // 该队伍所有成员 for (let i = teamPlayers.length - 1; i >= 0; i--) { // 遍历删除 if (teamPlayers[i] === player) { teamPlayers.splice(i, 1); } } }; /** * 玩家换队 * @param {Player} player 玩家 * @param {string} newTeamColor 队伍颜色 */ operations.changeTeam = function (player, newTeamColor) { // 玩家换队 operations.removePlayer(player); // 从原队伍中删除 player.teamColor = newTeamColor; // 改变队伍颜色 operations.addPlayer(player); // 增加到新队伍中 }; /** * 玩家死亡 * @param {Player} player 玩家 */ operations.playerDead = function (player) { const teamColor = player.teamColor, teamPlayers = players[teamColor]; // 玩家所在队伍 let all_dead = true; for (let i = 0; i < teamPlayers.length; i++) { if (teamPlayers[i].state !== 'dead') { all_dead = false; break; } } if (all_dead) { // 全部死亡 for (let i = 0; i < teamPlayers.length; i++) { teamPlayers[i].lose(); // 本队所有玩家 lose } for (const color in players) { if (color !== teamColor) { const teamPlayers = players[color]; // 其他队伍的玩家 for (let i = 0; i < teamPlayers.length; i++) { teamPlayers[i].win(); // 其他队伍所有玩家 win } } } } }; const reciveMessage = function () { // arguments 的第一个参数为消息名称 const message = Array.prototype.shift.call(arguments); operations[message].apply(this, arguments); }; return { reciveMessage } })();
可以看到,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他的玩家对象。我们还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化。
我们来看下测试结果:
// 红队: var player1 = playerFactory('皮蛋', 'red'), player2 = playerFactory('小乖', 'red'), player3 = playerFactory('宝宝', 'red'), player4 = playerFactory('小强', 'red'); // 蓝队: var player5 = playerFactory('黑妞', 'blue'), player6 = playerFactory('葱头', 'blue'), player7 = playerFactory('胖墩', 'blue'), player8 = playerFactory('海盗', 'blue'); player1.die(); player2.die(); player3.die(); player4.die();
运行结果如下。
皮蛋 lost
小乖 lost
宝宝 lost
小强 lost
黑妞 won
葱头 won
胖墩 won
海盗 won
假设皮蛋和小乖掉线
player1.remove(); player2.remove(); player3.die(); player4.die();
则结果如下。
宝宝 lost
小强 lost
黑妞 won
葱头 won
胖墩 won
海盗 won
假设皮蛋从红队叛变到蓝队
player1.changeTeam( 'blue' ); player2.die(); player3.die(); player4.die();
则结果如下。
小乖 lost
宝宝 lost
小强 lost
黑妞 won
葱头 won
胖墩 won
海盗 won
皮蛋 won
以上就是“JavaScript设计模式之中介者模式实例分析”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注蜗牛博客行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
评论