iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 14
0
Modern Web

JS Design Pattern 系列 第 14

JS Design Pattern Day14-中介者模式 Mediator(上)

嗨大家好,今天第14天,之前一直看youtube的一日軟體工程師系列(a day in the life of a software engineer),看完都會覺得很有幹勁很想當個軟體工程師,事實上我一到辦公室又是完全相反心境了...

那麼今天一起來練習 中介者模式 吧

中介者模式目的在於將所有物件之間的耦合透過一個中介都集合到中介者身上,讓所有物件與中介者之間是一對一的關係,但也只有跟中介者有關連。

實際做個小遊戲當範例,今天我們有兩個玩家互相為敵人關係,一方死亡另一方即獲勝:
先做玩家(Player)物件

var Player = function(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();
};

接下來設定遊戲玩家

var player1 = new Player('A');
var player2 = new Player('B');
//互相設為敵人
player1.enemy = player2;
player2.enemy = player1;

開始遊戲,其中一位玩家死亡

player1.die();

結果當然就是會印出另一方獲勝啦!但是只有兩位玩家也太少了,我們今天要增加為多位玩家,一樣分為兩隊伍,一隊伍全滅另一隊伍即為獲勝。
我們再修改一下玩家物件,讓玩家多依些屬性:隊友列表、敵人列表、玩家當前狀態、角色名稱、以及隊伍顏色

var Player = function (name, teamColor) {
	this.name = name;
	this.enemies = []; //敵人列表
	this.partners = []; //隊友列表
	this.isAlive = true; //玩家當前狀態
	this.teamColor = teamColor;
};
//勝利方式一樣
Player.prototype.win = function () {
	console.log(this.name + ' won');
};
//失敗方式一樣
Player.prototype.lose = function () {
	console.log(this.name + ' lost');
};

死亡方式會有點複雜,自己死亡之後要檢視一輪所有的隊友,如果都死亡代表敵方獲勝

Player.prototype.die = function () {
	var isAllDead = true;
	this.isAlive = false;
	this.partners.some(function (partner) {
		if (partner.isAlive) {
			isAllDead = false;
			return true;
		}
	});
	if (isAllDead) {
		this.lose();
		this.partners.forEach(function (partner) {
			partner.lose();
		});
		this.enemies.forEach(function (enemy) {
			enemy.win();
		});
	}

};

再來我們要做一個player工廠,過程中會多做一個陣列用來記錄所有的玩家,工廠裡每次製作都會依照隊伍顏色來設定隊友或是敵人

var allPlayers = [];
var playerFactory = function (name, teamColor) {
	var newPlayer = new Player(name, teamColor);
	allPlayers.forEach(function (player) {
		if (player.teamColor === teamColor) {
			player.partners.push(newPlayer);
			newPlayer.partners.push(player);
		} else {
			player.enemies.push(newPlayer);
			newPlayer.enemies.push(player);
		}
	});
	allPlayers.push(newPlayer);
	return newPlayer;
};

好了完成,我們來建立所有玩家

var p1 = playerFactory('A', 'red');
var p2 = playerFactory('B', 'red');
var p3 = playerFactory('C', 'red');
var p4 = playerFactory('D', 'blue');
var p5 = playerFactory('E', 'blue');
var p6 = playerFactory('F', 'blue');

假設紅隊全死光

p1.die();
p2.die();
p3.die();

最後結果就會印出所有贏家與輸家。現在我們可以一直新增玩家了,但是每一次更新狀態(假設我們每個玩家都有個等級屬性)就必須整個掃描一次所有的敵人或是隊友,因為每個物件之間都會透過enemies與partners這兩個清單來互相關聯,但如果現在玩家好幾百人或是有各種不同狀態就會嚴重影響效能。

那我們用中介者模式來修改:
首先我們要一樣做player物件,所有的操作都交給中介者物件來執行,我們把中介者命名為playerDirector

var Player = function (name, teamColor) {
    this.name = name;
    this.isAlive = true; //玩家當前狀態
    this.teamColor = teamColor;
};
//勝利方式一樣
Player.prototype.win = function () {
    console.log(this.name + ' won');
};
//失敗方是一樣
Player.prototype.lose = function () {
    console.log(this.name + ' lost');
};

死亡的話就透過中介者通知有玩家死亡

Player.prototype.die = function () {
    this.isAlive = false;
    playerDirector.ReceiveMessage('playerDead', this);
};

工廠有新增玩家也是透過中介者

var playerFactory = function (name, teamColor) {
    var newPlayer = new Player(name, teamColor);
    playerDirector.ReceiveMessage('addPlayer', newPlayer);
    return newPlayer;
};

接下來實作中介者,其實原理就是只維護一份玩家清單,讓每次狀態更新都是變動此清單

var playerDirector = (function () {
    var allPlayers = {};//玩家清單變成物件,因為key要換成teamColor
    var operations = {};//訊息處理物件
    //訊息處理物件實作新增玩家與玩家死亡的實際行為
    operations.addPlayer = function (player) {
        var teamColor = player.teamColor;
        allPlayers[teamColor] = allPlayers[teamColor] || [];
        allPlayers[teamColor].push(player);
    };
    operations.playerDead = function (player) {
        var teamColor = player.teamColor;
        var teamPlayers = allPlayers[teamColor];
        var isAllDead = true;

        teamPlayers.some(function (partner) {
            if (partner.isAlive) {
                isAllDead = false;
                return true;
            }
        });
        if (isAllDead) {
            teamPlayers.forEach(function (partner) {
                partner.lose();
            });
            for (var color in allPlayers) {
                if (color !== teamColor) {
                    var otherColorTeamPlayers = allPlayers[color];
                    otherColorTeamPlayers.forEach(function (enemy) {
                        enemy.win();
                    });
                }
            }
        }
    };
    //處理呼叫參數的介面
    var ReceiveMessage = function () {
        var message = Array.prototype.shift.call(arguments);
        operations[message].apply(this, arguments);
    }
    return {
        ReceiveMessage: ReceiveMessage
    };
})();

實際使用當然會跟原本結果一樣

//建立所有玩家
var p1 = playerFactory('A', 'red');
var p2 = playerFactory('B', 'red');
var p3 = playerFactory('C', 'red');
var p4 = playerFactory('D', 'blue');
var p5 = playerFactory('E', 'blue');
var p6 = playerFactory('F', 'blue');
//紅隊全死光
p1.die();
p2.die();
p3.die();

今天我們要新增某位玩家,舊的方法就要將每個物件都掃過一次調整裡面的隊友與敵人清單做添加,現在只要在中介者中來操作共同維護同一份清單就好囉!


上一篇
JS Design Pattern Day13-職責鏈模式 Chain of Responsibility(下)
下一篇
JS Design Pattern Day15-中介者模式 Mediator(下)
系列文
JS Design Pattern 30

尚未有邦友留言

立即登入留言