设计模式之-中介者模式

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

javascript 复制代码
// 泡泡堂游戏
// 先定义一个玩家类,有三个方法win,lose,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个玩家对象
const player1 = new Player('皮蛋');
const player2 = new Player('小乖');
// 给玩家相互设置敌人
player1.enemy=player2;
player2.enemy=player1;

player1.die(); // 输出皮蛋lost 小乖won
javascript 复制代码
// 为游戏增加队伍
const 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(){ // 玩家死亡
        var 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(); 
            }
        }
    }
}
// 最后定义一个工厂来创建玩家
const playerFactory = function(name,teamColor){
    const 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;
}

// 现在来感受一下,用这段代码创建8个玩家
// 红队:
const player1=playerFactory('皮蛋','red');
const player2=playerFactory('小乖','red');
const player3=playerFactory('宝宝','red');
const player4=playerFactory('小强','red');
// 蓝队:
const player5=playerFactory('黑妞','blue');
const player6=playerFactory('葱头','blue');
const player7=playerFactory('胖墩','blue');
const player8=playerFactory('海盗','blue');
console.log(players)

// 让红队玩家全部死亡
player1.die();
player2.die();
player3.die();
player4.die();

在上面这段代码中,每个玩家都有两个属性,this.partners和this.enemies,用来保存其他玩家对象的引用,当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡,都必须显式地遍历通知其他对象。这个例子只有8个对象,如果有成千个玩家,几十支队伍湖乡厮杀,有一个玩家掉线,必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家。或许还有解除队伍和添加到别的队伍的功能,红色玩家可以突然变成蓝色玩家,这不仅仅是循环能解决的问题了,下面用中介者模式来改造上面的代码

javascript 复制代码
// 泡泡堂游戏
class Player {
    constructor(name,teamColor){
        this.state='alive'; //玩家生存状态
        this.name=name; // 角色名字
        this.teamColor = teamColor; // 队伍颜色
    }
    win(){ // 玩家胜利
        console.log(this.name+' won');
    }
    lose(){ // 玩家失败
        console.log(this.name+' lost');
    }
    die(){ // 玩家死亡
        this.state='dead';
        playerDirector.ReceiveMessage('playerDead',this); //给中介者发送消息,玩家死亡
    }
    remove(){ // 移除玩家
        playerDirector.ReceiveMessage('removePlayer',this); // 给中介者发送消息,移除一个玩家
    }
    changeTeam(color){ // 玩家换队
        playerDirector.ReceiveMessage('changeTeam',this,color); // 给中介者发送消息,玩家换队
    }
}
// 最后定义一个工厂来创建玩家
const playerFactory = function(name,teamColor){
    const newPlayer = new Player(name,teamColor); // 创建新玩家
    playerDirector.ReceiveMessage('addPlayer',newPlayer);
    return newPlayer;
}
// 实现中介者对象playerDirector
const playerDirector=(function(){
    const players={}; // 保存所有玩家
    const 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; // 玩家队伍颜色
        let 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;
        let 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
                    }
                }
            }
        }
    };

    const ReceiveMessage = function(){
        let message = Array.prototype.shift.call(arguments); // 取出arguments的第一个参数
        operations[message].apply(this,arguments);
    };
    return {
        ReceiveMessage:ReceiveMessage
    }

})();

// 红队:
const player1=playerFactory('皮蛋','red');
const player2=playerFactory('小乖','red');
const player3=playerFactory('宝宝','red');
const player4=playerFactory('小强','red');
// 蓝队:
const player5=playerFactory('黑妞','blue');
const player6=playerFactory('葱头','blue');
const player7=playerFactory('胖墩','blue');
const player8=playerFactory('海盗','blue');

// 让红队玩家全部死亡
// player1.die();
// player2.die();
// player3.die();
// player4.die();

// 假设皮蛋和小乖掉线
// player1.remove();
// player2.remove();
// player3.die();
// player4.die();

// 假设皮蛋从红队叛变到蓝队
player1.changeTeam('blue');
player2.remove();
player3.die();
player4.die();

购买商品的demo

html 复制代码
<body>
    选择颜色:<select id="colorSelect">
        <option value="">请选择</option>
        <option value="red">红色</option>
        <option value="blue">蓝色</option>
    </select>
    输入购买数量:<input type="text" id="numberInput">

    您选择了颜色:<div id="colorInfo"></div><br />
    您输入了数量:<div id="numberInfo"></div><br />

    <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>
    <script>
        var colorSelect = document.getElementById('colorSelect');
        var numberInput = document.getElementById('numberInput');
        var colorInfo = document.getElementById('colorInfo');
        var numberInfo = document.getElementById('numberInfo');
        var nextBtn = document.getElementById('nextBtn');

        var goods = { // 手机库存
            'red': 3,
            'blue': 6
        };
        colorSelect.onchange = function () {
            var color = this.value; // 颜色
            var number = numberInput.value; // 数量

            var stock = goods[color]; // 该颜色手机对应的当前库存
            colorInfo.innerHTML = color;
            if (!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择手机颜色';
                return;
            }
            if (((number - 0) | 0) !== number - 0) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请输入正确的购买数量';
                return;
            }
            if (number > stock) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '库存不足';
                return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入购物车'

        }

        numberInput.oninput = function () {
            var color = colorSelect.value; // 颜色
            var number = this.value; // 数量
            console.log(number)
            var stock = goods[color]; // 该颜色手机对应的当前库存
            numberInfo.innerHTML = number;
            if (!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择手机颜色';
                return;
            }
            if (((number - 0) | 0) !== number - 0) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请输入正确的购买数量';
                return;
            }
            if (number > stock) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '库存不足';
                return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入购物车'
        }

    </script>
</body>

增加手机内存下拉选


还需要添加选择内存的事件

javascript 复制代码
		memorySelect.onchange = function () {
            var color = colorSelect.value; // 颜色
            var number = numberInput.value; // 数量
            var memory = this.value; // 内存
            var stock =  goods[color+'|' +memory];  // 该颜色手机对应的当前库存
            memoryInfo.innerHTML = memory;
            if (!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择手机颜色';
                return;
            }
             if (!memory) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择内存大小';
                return;
            }
            if (((number - 0) | 0) !== number - 0) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请输入正确的购买数量';
                return;
            }
            if (number > stock) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '库存不足';
                return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入购物车'
        }

我们只是增加一个内存的选择条件,就要改变如此多的代码,这是因为在目前的实现种,每个节点对象都是耦合在一起的,改变或者增加任何一个节点对象,都要通知与其相关的对象,现在用中介者来重构代码

html 复制代码
<body>
    选择颜色:<select id="colorSelect">
        <option value="">请选择</option>
        <option value="red">红色</option>
        <option value="blue">蓝色</option>
    </select>
     选择内存:<select id="memorySelect">
        <option value="">请选择</option>
        <option value="32G">32G</option>
        <option value="16G">16G</option>
    </select>
    输入购买数量:<input type="text" id="numberInput">
    您选择了颜色:<div id="colorInfo"></div><br />
    您选择了内存:<div id="memoryInfo"></div><br />
    您输入了数量:<div id="numberInfo"></div><br />

    <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>
    <script>
        var goods = { // 手机库存
            'red|32G': 3,
            'red|16G': 0,
            'blue|32G': 1,
            'blue|16G': 6,
        };
        var mediator=(function(){
            var colorSelect = document.getElementById('colorSelect');
            var memorySelect = document.getElementById('memorySelect');
            var numberInput = document.getElementById('numberInput');
            var colorInfo = document.getElementById('colorInfo');
            var memoryInfo = document.getElementById('memoryInfo');
            var numberInfo = document.getElementById('numberInfo');
            var nextBtn = document.getElementById('nextBtn');
            return {
                changed:function(obj){
                    var color = colorSelect.value; // 颜色
                    var number = numberInput.value; // 数量
                    var memory = memorySelect.value; // 内存
                    var stock = goods[color+'|' +memory]; // 该颜色手机对应的当前库存

                    if(obj===colorSelect){
                        colorInfo.innerHTML=color;
                    }else if(obj===memorySelect){
                        memoryInfo.innerHTML=memory;
                    }else if(obj===numberInput){
                        numberInfo.innerHTML=number;
                    }

                    if (!color) {
                        nextBtn.disabled = true;
                        nextBtn.innerHTML = '请选择手机颜色';
                        return;
                    }
                    if (!memory) {
                        nextBtn.disabled = true;
                        nextBtn.innerHTML = '请选择内存大小';
                        return;
                    }
                    if (((number - 0) | 0) !== number - 0) {
                        nextBtn.disabled = true;
                        nextBtn.innerHTML = '请输入正确的购买数量';
                        return;
                    }
                    if (number > stock) {
                        nextBtn.disabled = true;
                        nextBtn.innerHTML = '库存不足';
                        return;
                    }
                    nextBtn.disabled = false;
                    nextBtn.innerHTML = '放入购物车'
                }
            }
        })()


        colorSelect.onchange = function () {
            mediator.changed(this);
        }

        numberInput.oninput = function () {
           mediator.changed(this);
        }

         memorySelect.onchange = function () {
            mediator.changed(this);
        }

    </script>
</body>

可以想像,某天又要新增一些跟需求相关的节点,比如GPU型号,那么只需要改动mefiator对象即可。

相关推荐
杨充7 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充7 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充8 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充8 小时前
2.面向对象的特性
设计模式
杨充8 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充8 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充8 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯11 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯19 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式