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


Posted in Javascript onFebruary 15, 2017

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

日常生活中的列子:

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

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

实现中介者的列子如下:

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

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

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来给每个玩家设置队友或者敌人;

var players = [];

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

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",'red'),
  p3 = heroFactory("cc",'red'),
  p4 = heroFactory("dd",'red');
    
// 蓝队
var p5 = heroFactory("ee",'blue'),
  p6 = heroFactory("ff",'blue'),
  p7 = heroFactory("gg",'blue'),
  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 识别消息来自于那个玩家对象。

代码如下:

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'),
  p2 = heroFactory("bb",'red'),
  p3 = heroFactory("cc",'red'),
    p4 = heroFactory("dd",'red');
    
  // 蓝队
  var p5 = heroFactory("ee",'blue'),
    p6 = heroFactory("ff",'blue'),
    p7 = heroFactory("gg",'blue'),
    p8 = heroFactory("hh",'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代码如下:

选择颜色:

<select id="colorSelect">
    <option value="">请选择</option>
    <option value="red">红色</option>
    <option value="blue">蓝色</option>
  </select>
  <p>输入购买的数量: <input type="text" id="numberInput"/></p>
  你选择了的颜色:<div id="colorInfo"></div>
  <p>你输入的数量: <div id="numberInfo"></div> </p>
  <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>

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

我们先定义一下:

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

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

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

常规的JS代码如下:

// 假设我们提前从后台获取到所有颜色手机的库存量
var goods = {
  // 手机库存
  "red": 6,
  "blue": 8
};
/*
我们下面分别来监听colorSelect的下拉框的onchange事件和numberInput输入框的oninput的事件,
然后在这两个事件中作出相应的处理
*/
var colorSelect = document.getElementById("colorSelect"),
  numberInput = document.getElementById("numberInput"),
  colorInfo = document.getElementById("colorInfo"),
  numberInfo = document.getElementById("numberInfo"),
  nextBtn = document.getElementById("nextBtn");
    
// 监听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代码如下:

选择颜色:
  <select id="colorSelect">
    <option value="">请选择</option>
    <option value="red">红色</option>
    <option value="blue">蓝色</option>
  </select>
  <br/>
  <br/>
  选择内存:
  <select id="memorySelect">
    <option value="">请选择</option>
    <option value="32G">32G</option>
    <option value="64G">64G</option>
  </select>
  <p>输入购买的数量: <input type="text" id="numberInput"/></p>
  你选择了的颜色:<div id="colorInfo"></div>
  你选择了内存:<div id="memoryInfo"></div>
  <p>你输入的数量: <div id="numberInfo"></div> </p>
  <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>

JS代码变为如下:

// 假设我们提前从后台获取到所有颜色手机的库存量
var goods = {
  // 手机库存
  "red|32G": 6,
  "red|64G": 16,
  "blue|32G": 8,
  "blue|64G": 18
};
/*
我们下面分别来监听colorSelect的下拉框的onchange事件和numberInput输入框的oninput的事件,
然后在这两个事件中作出相应的处理
 */
var colorSelect = document.getElementById("colorSelect"),
  memorySelect = document.getElementById("memorySelect"),
  numberInput = document.getElementById("numberInput"),
  colorInfo = document.getElementById("colorInfo"),
  numberInfo = document.getElementById("numberInfo"),
  memoryInfo = document.getElementById("memoryInfo"),
  nextBtn = document.getElementById("nextBtn");
    
// 监听change事件
colorSelect.onchange = function(){
  select();
};
numberInput.oninput = function(){
  select();
};
memorySelect.onchange = function(){
  select();  
};
function select(){
  var color = colorSelect.value,  // 颜色
    number = numberInput.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 = "放入购物车";
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS网络游戏-(模拟城市webgame)提供的一些例子下载
Oct 14 Javascript
jquery动感漂浮导航菜单代码分享
Apr 15 Javascript
js实现简洁的滑动门菜单(选项卡)效果代码
Sep 04 Javascript
JavaScript和HTML DOM的区别与联系及Javascript和DOM的关系
Nov 15 Javascript
js仿微博实现统计字符和本地存储功能
Dec 22 Javascript
基于javascript实现样式清新图片轮播特效
Mar 30 Javascript
前端性能优化及技巧
May 06 Javascript
浅谈layui数据表格判断问题(加入表单元素),设置单元格样式
Oct 26 Javascript
JS脚本实现定时到网站上签到/签退功能
Apr 22 Javascript
微信小程序实现多选框功能的实例代码
Jun 24 Javascript
vue过滤器实现日期格式化的案例分析
Jul 02 Javascript
vue3+typeScript穿梭框的实现示例
Dec 29 Vue.js
javascript中apply/call和bind的使用
Feb 15 #Javascript
JS实现图片放大缩小的方法
Feb 15 #Javascript
JavaScript中的编码和解码函数
Feb 15 #Javascript
js date 格式化
Feb 15 #Javascript
JS实现最简单的冒泡排序算法
Feb 15 #Javascript
javascript设计模式之单体模式学习笔记
Feb 15 #Javascript
JavaScript中的toString()和toLocaleString()方法的区别
Feb 15 #Javascript
You might like
定制404错误页面,并发信给管理员的程序
2006/10/09 PHP
php 去除html标记--strip_tags与htmlspecialchars的区别详解
2013/06/26 PHP
CI框架常用经典操作类总结(路由,伪静态,分页,session,验证码等)
2016/11/21 PHP
在多个页面使用同一个HTML片段的代码
2011/03/04 Javascript
jQuery实现切换页面布局使用介绍
2011/10/09 Javascript
JavaScript修改css样式style动态改变元素样式
2013/12/16 Javascript
jQuery中slideUp()方法用法分析
2014/12/24 Javascript
js中this的用法实例分析
2015/01/10 Javascript
移动端JQ插件hammer使用详解
2015/07/03 Javascript
直接拿来用的15个jQuery代码片段
2015/09/23 Javascript
js剪切板应用clipboardData实例解析
2016/05/29 Javascript
JS简单测试循环运行时间的方法
2016/09/04 Javascript
JS中位置与大小的获取方法
2016/11/22 Javascript
JavaScript使用ZeroClipboard操作剪切板
2017/05/10 Javascript
angular过滤器实现排序功能
2017/06/27 Javascript
Node.js 回调函数实例详解
2017/07/06 Javascript
详解从买域名到使用pm2部署node.js项目全过程
2018/03/07 Javascript
Vue表单demo v-model双向绑定问题
2018/06/29 Javascript
vue3弹出层V3Popup实例详解
2021/01/04 Vue.js
解决Python的str强转int时遇到的问题
2018/04/09 Python
Flask和Django框架中自定义模型类的表名、父类相关问题分析
2018/07/19 Python
tensorflow 中对数组元素的操作方法
2018/07/27 Python
Python实现SQL注入检测插件实例代码
2019/02/02 Python
对python中不同模块(函数、类、变量)的调用详解
2019/07/16 Python
Python 创建TCP服务器的方法
2020/07/28 Python
Html5游戏开发之乒乓Ping Pong游戏示例(三)
2013/01/21 HTML / CSS
JD Sports荷兰:英国领先的运动时尚零售商
2020/03/13 全球购物
什么是属性访问器
2015/10/26 面试题
Java中的类包括什么内容?设计时要注意哪些方面
2012/05/23 面试题
测控技术与通信工程毕业生自荐信范文
2013/12/28 职场文书
党员思想汇报范文
2013/12/30 职场文书
党员培训思想汇报
2014/01/07 职场文书
竞选班长的演讲稿
2014/04/24 职场文书
宾馆仓管员岗位职责
2014/07/27 职场文书
志愿者事迹材料
2014/12/26 职场文书
面试官问我Mysql的存储引擎了解多少
2022/08/05 MySQL