JavaScript设计模式-中介者模式

概念

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

描述

  现实生活中有很多中介者的例子。如:在世界杯期间购买足球彩票,上千万的人一起计算赔率和输赢是不可能的,博彩公司作中介,每个人只需要与博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就把钱交给博彩公司。

应用

  我们来用JS模拟泡泡堂游戏。
【初始版本】
  Play.prototype.winPlay.prototype.lose表示玩家输赢以及表示玩家死亡Play.prototype.die。因为玩家的数目是2,所以当其中一个玩家死亡的时候游戏便结束,同时通知它的对手胜利。

class Player {
    constructor(name) {
        this.name = name
        this.enemy = null
    }
    win() {
        console.log(this.name + ' won ')
    }
    lose() {
        console.log(this.name + ' lost')
    }
    die() {
        this.lose()
        this.enemy.win()
    }
}

  接下来创建2个玩家对象:

let player1 = new Player( 'hello' )
let player2 = new Player( 'world' )

  给玩家相互设置敌人:

player1.enemy = player2;
player2.enemy = player1;

  当玩家player1被泡泡炸死的时候,只需要调用这一句代码便完成了一局游戏:

player1.die()

  上面代码只是2个玩家,真正的泡泡堂游戏可以有8个玩家,并分成红蓝两队进行游戏。
  定义一个数组players来保存所有的玩家,在创建玩家之后,循环players来给每个玩家设置队友和敌人:

let players = []

  为每个玩家对象都增加一些属性,分别是队友列表、敌人列表、玩家当前状态、角色名字以及玩家所在的队伍颜色:

class Player {
    constructor(name, teamColor) {
        this.partners = [] // 队友列表
        this.enemies = [] // 敌人列表
        this.state = 'live' // 玩家状态
        this.name = name // 角色名字
        this.teamColor = teamColor // 队伍颜色
    }
    win() { // 玩家团队胜利
        console.log( 'winner: ' + this.name )
    }
    lose() { // 玩家团队失败
        console.log( 'loser: ' + this.name )
    }
    die() { // 玩家死亡的方法要变得稍微复杂一点,需要在每个玩家死亡的时候,都遍历其他队友的生存状况,
            // 如果队友全部死亡,则这局游戏失败,同时敌人队伍的所有玩家都取得胜利
        let all_dead = true
        this.state = 'dead' // 设置玩家状态为死亡
        for ( let i = 0, partner; partner = this.partners[ i++ ]; ){ // 遍历队友列表
            if ( partner.state !== 'dead' ){ // 如果还有一个队友没有死亡,则游戏还未失败
                all_dead = false
                break
            }
        }
        if ( all_dead === true ){ // 如果队友全部死亡
            this.lose() // 通知自己游戏失败
            for ( let i = 0, partner; partner = this.partners[ i++ ]; ){ // 通知所有队友玩家游戏失败
                partner.lose()
            }
            for ( let i = 0, enemy; enemy = this.enemies[ i++ ]; ){ // 通知所有敌人游戏胜利
                enemy.win()
            }
        }
    }

}

  定义一个工厂来创建玩家:

let playerFactory = function( name, teamColor ){
    let newPlayer = new Player( name, teamColor ) // 创建新玩家
    for ( let i = 0, player; player = players[ i++ ]; ){ // 通知所有的玩家,有新角色加入
        if ( player.teamColor === newPlayer.teamColor ){ // 如果是同一队的玩家
            player.partners.push( newPlayer ) // 相互添加到队友列表
            newPlayer.partners.push( player )
        }else{
            player.enemies.push( newPlayer ) // 相互添加到敌人列表
            newPlayer.enemies.push( player )
        }
    }
    players.push( newPlayer )
    return newPlayer
}
//红队:
let player1 = playerFactory( '北京', 'red' ),
    player2 = playerFactory( '天津', 'red' ),
    player3 = playerFactory( '郑州', 'red' ),
    player4 = playerFactory( '青岛', 'red' )
//蓝队:
let player5 = playerFactory( '上海', 'blue' ),
    player6 = playerFactory( '深圳', 'blue' ),
    player7 = playerFactory( '杭州', 'blue' ),
    player8 = playerFactory( '广州', 'blue' )

  现在已经可以随意地为游戏增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧紧耦合在一起的。在此段代码中,每个玩家对象都有两个属性,this.partnersthis.enemies,用来保存其他玩家对象的引用。当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡时,都必须要显式地遍历通知其他对象。
  如果在一个大型网络游戏中,画面里有成百上千个玩家,几十支队伍在互相厮杀。如果有一个玩家掉线,必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家。游戏也许还有解除队伍和添加到别的队伍的功能,红色玩家可以突然变成蓝色玩家,这就不再仅仅是循环能够解决的问题了。

【中介者模式】
  现在开始用中介者模式来改造上面的泡泡堂游戏,首先仍然是定义Player构造函数和player对象的原型方法,在player对象的这些原型方法中,不再负责具体的执行逻辑,而是把操作转交给中介者对象,把中介者对象命名为playerDirector:

class Player {
    constructor(name, teamColor) {
        this.name = name
        this.teamColor = teamColor
        this.state = 'live'
    }
    win() {
        console.log( this.name + ' won ' )
    }
    lose() {
        console.log( this.name +' lost' )
    }
    die() {
        this.state = 'dead'
        playerDirector.reciveMessage( 'playerDead', this ) // 给中介者发送消息,玩家死亡
    }
    remove() {
        playerDirector.reciveMessage( 'removePlayer', this ) // 给中介者发送消息,移除一个玩家
    }
    changeTeam(color) {
        playerDirector.reciveMessage( 'changeTeam', this, color ) // 给中介者发送消息,玩家换队

    }
}

  再继续改写之前创建玩家对象的工厂函数,可以看到,因为工厂函数里不再需要给创建的玩家对象设置队友和敌人,这个工厂函数几乎失去了工厂的意义:

let playerFactory=function(name,teamColor){
    let newPlayer=new Player(name,teamColor)   //创造一个新的玩家对象
    playerDirector.reciveMessage('addPlayer',newPlayer)    //给中介者发送消息,新增玩家
    return newPlayer
}

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

let playerDirector= ( function(){
    let players = {}, // 保存所有玩家
        operations = {} // 中介者可以执行的操作
    /****************新增一个玩家***************************/
    operations.addPlayer = function( player ){
        let teamColor = player.teamColor // 玩家的队伍颜色
        players[ teamColor ] = players[ teamColor ] || [] // 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
        players[ teamColor ].push( player ) // 添加玩家进队伍
    }
    /****************移除一个玩家***************************/
    operations.removePlayer = function( player ){
        let teamColor = player.teamColor, // 玩家的队伍颜色
            teamPlayers = players[ teamColor ] || [] // 该队伍所有成员
        for ( let 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 ){ // 玩家死亡
        let teamColor = player.teamColor,
            teamPlayers = players[ teamColor ] // 玩家所在队伍
        let all_dead = true
        for ( let i = 0, player; player = teamPlayers[ i++ ]; ){
            if ( player.state !== 'dead' ){
                all_dead = false
                break
            }
        }
        if ( all_dead === true ){ // 全部死亡
            for ( let i = 0, player; player = teamPlayers[ i++ ]; ){
                player.lose() // 本队所有玩家lose
            }
            for ( let color in players ){
                if ( color !== teamColor ){
                    let teamPlayers = players[ color ] // 其他队伍的玩家
                    for ( let i = 0, player; player = teamPlayers[ i++ ]; ){
                        player.win() // 其他队伍所有玩家win
                    }
                }
            }
        }
    }
    let reciveMessage = function(){
        let message = Array.prototype.shift.call( arguments ) // arguments 的第一个参数为消息名称
        operations[ message ].apply( this, arguments )
    }

    return {
        reciveMessage: reciveMessage
    }
})()

  可以看到,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他的玩家对象。还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化。

小结

  中介者模式符合迪米特法。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。如果对象之间的耦合性太高,一个对象发生改变之后,难免会影响到其他的对象。而在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方。因此,中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。不过,中介者模式也存在一些缺点。其中,最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。

参考文献

《JavaScript设计模式与开发实践》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 中介者模式:通过中介者对象封装一些列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合。 中介者和观察者对...
    蟹老板爱写代码阅读 659评论 1 0
  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 4,036评论 0 13
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,717评论 2 17
  • 接近凌晨这个城市依旧喧嚣, 每个夜晚都有人喝了酒使劲嚷嚷, 反复反锁的防盗门依旧给不了安全感 恐惧…… 这个被利益...
    等哈哈儿阅读 112评论 0 0
  • 这天太冷了。 路晨压低帽沿,裹紧大衣,急匆匆的走在街上。 夜已经很深了,街两旁的店铺都已打烊,四周静悄悄地没有声音...
    善下归海阅读 558评论 0 5