javascript设计模式之中介者模式学习笔记

先来理解这么一个问题,假如我们前端开发接的需求是需求方给我们需求,可能一个前端开发会和多个需求方打交道,所以会保持多个需求方的联系,那么在程序里面就意味着保持多个对象的引用,当程序的规模越大,对象会越来越多,他们之间的关系会越来越复杂,那现在假如现在有一个中介者(假如就是我们的主管)来对接多个需求方的需求,那么需求方只需要把所有的需求给我们主管就可以,主管会依次看我们的工作量来给我们分配任务,这样的话,我们前端开发就不需要和多个业务方联系,我们只需要和我们主管(也就是中介)联系即可,这样的好处就弱化了对象之间的耦合。

日常生活中的列子:

中介者模式对于我们日常生活中经常会碰到,比如我们去房屋中介去租房,房屋中介人在租房者和房东出租者之间形成一条中介;租房者并不关心租谁的房,房东出租者也并不关心它租给谁,因为有中介,所以需要中介来完成这场交易。

中介者模式的作用是解除对象与对象之间的耦合关系,增加一个中介对象后,所有的相关对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发送改变时,只需要通知中介者对象即可。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。

实现中介者的列子如下:

不知道大家有没有玩过英雄杀这个游戏,最早的时候,英雄杀有2个人(分别是敌人和自己);我们针对这个游戏先使用普通的函数来实现如下:

比如先定义一个函数,该函数有三个方法,分别是win(赢),lose(输),和die(敌人死亡)这三个函数;只要一个玩家死亡该游戏就结束了,同时需要通知它的对手胜利了; 代码需要编写如下:

rush:js;"> function Hero(name) { this.name = name; this.enemy = null; } Hero.prototype.win = function(){ console.log(this.name + 'Won'); } Hero.prototype.lose = function(){ console.log(this.name + 'lose'); } Hero.prototype.die = function(){ this.lose(); this.enemy.win(); } // 初始化2个对象 var h1 = new Hero("朱元璋"); var h2 = new Hero("刘伯温"); // 给玩家设置敌人 h1.enemy = h2; h2.enemy = h1; // 朱元璋死了 也就输了 h1.die(); // 输出 朱元璋lose 刘伯温Won

现在我们再来为游戏添加队友

比如现在我们来为游戏添加队友,比如英雄杀有6人一组,那么这种情况下就有队友,敌人也有3个;因此我们需要区分是敌人还是队友需要队的颜色这个字段,如果队的颜色相同的话,那么就是同一个队的,否则的话就是敌人;

我们可以先定义一个数组players来保存所有的玩家,在创建玩家之后,循环players来给每个玩家设置队友或者敌人;

接着我们再来编写Hero这个函数代码如下:

rush:js;"> var players = []; // 定义一个数组 保存所有的玩家 function Hero(name,teamColor) { this.friends = []; //保存队友列表 this.enemies = []; // 保存敌人列表 this.state = 'live'; // 玩家状态 this.name = name; // 角色名字 this.teamColor = teamColor; // 队伍的颜色 } Hero.prototype.win = function(){ // 赢了 console.log("win:" + this.name); }; Hero.prototype.lose = function(){ // 输了 console.log("lose:" + this.name); }; Hero.prototype.die = function(){ // 所有队友死亡情况 认都是活着的 var all_dead = true; this.state = 'dead'; // 设置玩家状态为死亡 for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) { // 遍历,如果还有一个队友没有死亡的话,则游戏还未结束 if(this.friends[i].state !== 'dead') { all_dead = false; break; } } if(all_dead) { this.lose(); // 队友全部死亡,游戏结束 // 循环 通知所有的玩家 游戏失败 for(var j = 0,jlen = this.friends.length; j < jlen; j+=1) { this.friends[j].lose(); } // 通知所有敌人游戏胜利 for(var j = 0,jlen = this.enemies.length; j < jlen; j+=1) { this.enemies[j].win(); } } } // 定义一个工厂类来创建玩家 var heroFactory = function(name,teamColor) { var newPlayer = new Hero(name,teamColor); for(var i = 0,ilen = players.length; i < ilen; i+=1) { // 如果是同一队的玩家 if(players[i].teamColor === newPlayer.teamColor) { // 相互添加队友列表 players[i].friends.push(newPlayer); newPlayer.friends.push(players[i]); }else { // 相互添加到敌人列表 players[i].enemies.push(newPlayer); newPlayer.enemies.push(players[i]); } } players.push(newPlayer); return newPlayer; }; // 红队 var p1 = heroFactory("aa",'red'),p2 = heroFactory("bb",p3 = heroFactory("cc",p4 = heroFactory("dd",'red');

// 蓝队
var p5 = heroFactory("ee",'blue'),p6 = heroFactory("ff",p7 = heroFactory("gg",p8 = heroFactory("hh",'blue');
// 让红队玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:dd lose:aa lose:bb lose:cc
// win:ee win:ff win:gg win:hh

如上代码:Hero函数有2个参数,分别是name(玩家名字)和teamColor(队颜色),

首先我们可以根据队颜色来判断是队友还是敌人;同样也有三个方法win(赢),lose(输),和die(死亡);如果每次死亡一个人的时候,循环下该死亡的队友有没有全部死亡,如果全部死亡了的话,就输了,因此需要循环他们的队友,分别告诉每个队友中的成员他们输了,同时需要循环他们的敌人,分别告诉他们的敌人他们赢了;因此每次死了一个人的时候,都需要循环一次判断他的队友是否都死亡了;因此每个玩家和其他的玩家都是紧紧耦合在一起了。

下面我们可以使用中介者模式来改善上面的demo;

首先我们仍然定义Hero构造函数和Hero对象原型的方法,在Hero对象的这些原型方法中,不再负责具体的执行的逻辑,而是把操作转交给中介者对象,中介者对象来负责做具体的事情,我们可以把中介者对象命名为playerDirector;

在playerDirector开放一个对外暴露的接口ReceiveMessage,负责接收player对象发送的消息,而player对象发送消息的时候,总是把自身的this作为参数发送给playerDirector,以便playerDirector 识别消息来自于那个玩家对象。

代码如下:

<div class="jb51code">
<pre class="brush:js;">
var players = []; // 定义一个数组 保存所有的玩家
function Hero(name,teamColor) {
this.state = 'live'; // 玩家状态
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍的颜色
}
Hero.prototype.win = function(){
// 赢了
console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
// 输了
console.log("lose:" + this.name);
};
// 死亡
Hero.prototype.die = function(){
this.state = 'dead';
// 给中介者发送消息,玩家死亡
playerDirector.ReceiveMessage('playerDead',this);
}
// 移除玩家
Hero.prototype.remove = function(){
// 给中介者发送一个消息,移除一个玩家
playerDirector.ReceiveMessage('removePlayer',this);
};
// 玩家换队
Hero.prototype.changeTeam = function(color) {
// 给中介者发送一个消息,玩家换队
playerDirector.ReceiveMessage('changeTeam',this,color);
};
// 定义一个工厂类来创建玩家
var heroFactory = function(name,teamColor) {
// 创建一个新的玩家对象
var newHero = new Hero(name,teamColor);
// 给中介者发送消息,新增玩家
playerDirector.ReceiveMessage('addPlayer',newHero);
return newHero;
};
var playerDirector = (function(){
var players = {},// 保存所有的玩家
operations = {}; // 中介者可以执行的操作
// 新增一个玩家操作
operations.addPlayer = function(player) {
// 获取玩家队友的颜色
var teamColor = player.teamColor;
// 如果该颜色的玩家还没有队伍的话,则新成立一个队伍
players[teamColor] = players[teamColor] || [];
// 添加玩家进队伍
players[teamColor].push(player);
};
// 移除一个玩家
operations.removePlayer = function(player){
// 获取队伍的颜色
var teamColor = player.teamColor,// 获取该队伍的所有成员
teamPlayers = players[teamColor] || [];
// 遍历
for(var i = teamPlayers.length - 1; i>=0; i--) {
if(teamPlayers[i] === player) {
teamPlayers.splice(i,1);
}
}
};
// 玩家换队
operations.changeTeam = function(player,newTeamColor){
// 首先从原队伍中删除
operations.removePlayer(player);
// 然后改变队伍的颜色
player.teamColor = newTeamColor;
// 增加到队伍中
operations.addplayer(player);
};
// 玩家死亡
operations.playerDead = function(player) {
var teamColor = player.teamColor,// 玩家所在的队伍
teamPlayers = players[teamColor];

var all_dead = true;
//遍历
for(var i = 0,player; player = teamPlayers[i++]; ) {
if(player.state !== 'dead') {
all_dead = false;
break;
}
}
// 如果all_dead 为true的话 说明全部死亡
if(all_dead) {
for(var i = 0,player; player = teamPlayers[i++]; ) {
// 本队所有玩家lose
player.lose();
}
for(var color in players) {
if(color !== teamColor) {
// 说明这是另外一组队伍
// 获取该队伍的玩家
var teamPlayers = players[color];
for(var i = 0,player; player = teamPlayers[i++]; ) {
player.win(); // 遍历通知其他玩家win了
}
}
}
}
};
var ReceiveMessage = function(){
// arguments的第一个参数为消息名称 获取一个参数
var message = Array.prototype.shift.call(arguments);
operations[message].apply(this,arguments);
};
return {
ReceiveMessage : ReceiveMessage
};
})();
// 红队
var p1 = heroFactory("aa",'red');

// 蓝队
var p5 = heroFactory("ee",'blue');
// 让红队玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:aa lose:bb lose:cc lose:dd
// win:ee win:ff win:gg win:hh

我们可以看到如上代码;玩家与玩家之间的耦合代码已经解除了,而把所有的逻辑操作放在中介者对象里面进去处理,某个玩家的任何操作不需要去遍历去通知其他玩家,而只是需要给中介者发送一个消息即可,中介者接受到该消息后进行处理,处理完消息之后会把处理结果反馈给其他的玩家对象。使用中介者模式解除了对象与对象之间的耦合代码; 使程序更加的灵活.

中介者模式实现购买商品的列子

下面的列子是书上的列子,比如在淘宝或者天猫的列子不是这样实现的,也没有关系,我们可以改动下即可,我们最主要来学习下使用中介者模式来实现的思路。

首先先介绍一下业务:在购买流程中,可以选择手机的颜色以及输入购买的数量,同时页面中有2个展示区域,分别显示用户刚刚选择好的颜色和数量。还有一个按钮动态显示下一步的操作,我们需要查询该颜色手机对应的库存,如果库存数量小于这次的购买数量,按钮则被禁用并且显示库存不足的文案,反之按钮高亮且可以点击并且显示假如购物车。

HTML代码如下:

选择颜色:

rush:xhtml;">

输入购买的数量: 你选择了的颜色:

你输入的数量:

首先页面上有一个select选择框,然后有输入的购买数量输入框,还有2个展示区域,分别是选择的颜色和输入的数量显示的区域,还有下一步的按钮操作;

我们先定义一下:

假设我们提前从后台获取到所有颜色手机的库存量

rush:js;"> var goods = { // 手机库存 "red": 6,"blue": 8 };

接着 我们下面分别来监听colorSelect的下拉框的onchange事件和numberInput输入框的oninput的事件,然后在这两个事件中作出相应的处理

常规的JS代码如下:

// 监听change事件
colorSelect.onchange = function(e){
select();
};
numberInput.oninput = function(){
select();
};
function select(){
var color = colorSelect.value,// 颜色
number = numberInput.value,// 数量
stock = goods[color]; // 该颜色手机对应的当前库存

colorInfo.innerHTML = color;
numberInfo.innerHTML = number;

// 如果用户没有选择颜色的话,禁用按钮
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "请选择手机颜色";
return;
}
// 判断用户输入的购买数量是否是正整数
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "请输入正确的购买数量";
return;
}
// 如果当前选择的数量大于当前的库存的数量的话,显示库存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "库存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入购物车";
}

上面的代码虽然是完成了页面上的需求,但是我们的代码都耦合在一起了,目前虽然问题不是很多,假如随着以后需求的改变,SKU属性越来越多的话,比如页面增加一个或者多个下拉框的时候,代表选择手机内存,现在我们需要计算颜色,内存和购买数量,来判断nextBtn是显示库存不足还是放入购物车;代码如下:

HTML代码如下:

rush:xhtml;"> 选择颜色:

选择内存:

输入购买的数量: 你选择了的颜色:

你选择了内存:

你输入的数量:

JS代码变为如下:

// 监听change事件
colorSelect.onchange = function(){
select();
};
numberInput.oninput = function(){
select();
};
memorySelect.onchange = function(){
select();
};
function select(){
var color = colorSelect.value,// 数量
memory = memorySelect.value,// 内存
stock = goods[color + '|' +memory]; // 该颜色手机对应的当前库存

colorInfo.innerHTML = color;
numberInfo.innerHTML = number;
memoryInfo.innerHTML = memory;
// 如果用户没有选择颜色的话,禁用按钮
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "请选择手机颜色";
return;
}
// 判断用户输入的购买数量是否是正整数
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "请输入正确的购买数量";
return;
}
// 如果当前选择的数量大于当前的库存的数量的话,显示库存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "库存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入购物车";
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式。‌在聊深浅拷贝之前咱得了解一下js中的两种数据类型:
前言 今天复习了一些前端算法题,写到一两道比较有意思的题:重建二叉树、反向输出链表每个节点 题目 重建二叉树: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列 {1,2,4,7,3,5,6,8} 和中序遍历序列 {
最近在看回JavaScript的面试题,this 指向问题是入坑前端必须了解的知识点,现在迎来了ES6+的时代,因为箭头函数的出现,所以感觉有必要对 this 问题梳理一下,所以刚好总结一下JavaScript中this指向的问题。
js如何实现弹出form提交表单?(图文+视频)
js怎么获取复选框选中的值
js如何实现倒计时跳转页面
如何用js控制图片放大缩小
JS怎么获取当前时间戳
JS如何判断对象是否为数组
JS怎么获取图片当前宽高
JS对象如何转为json格式字符串
JS怎么获取图片原始宽高
怎么在click事件中调用多个js函数
js如何往数组中添加新元素
js如何拆分字符串
JS怎么对数组内元素进行求和
JS如何判断屏幕大小
js怎么解析json数据
js如何实时获取浏览器窗口大小
原生JS实现别踩白块小游戏(五)