意圖
用一個中介對象來封裝一系列對象的交互。中介者使得各對象不需要顯式相互引用,從而使其松散耦合,而且可以獨立地改變它們之間的交互。
場景
我們知道,一個網絡游戲往往有很多大區。每一個大區可以是一組服務器,也可以是多組服務器,在這裡假設一個大區是一組服務器。為了效率,一般每個大區都會有一個數據庫,玩家的創建角色、充值、消費行為只是在這一個大區中有效。現在公司有了新的需求,那就是玩家的一些信息能在多個大區中共享。比如,在注冊的時候就把玩家的賬戶信息寫入多個信息共享的大區,玩家在某個大區中充值需要“通知”其它大區修改賬戶余額,玩家在某個大區中消費也需要“通知”其它大區修改賬戶余額。
如果我們現在有ABC三個大區,下面的方法可以實現需求:
l 網站的注冊方法調用A、B和C大區的注冊方法
l A大區的充值方法調用B和C的充值方法
l B大區的充值方法調用A和C的充值方法
l C大區的充值方法調用A和B的充值方法
l A大區的消費方法調用B和C的充值方法
l B大區的消費方法調用A和C的充值方法
l C大區的消費方法調用A和B的充值方法
我想,沒有人會這麼做吧。你肯定會想到在一個統一的地方去維護所有大區的信息,任何一個大區的行為不直接和其它大區的行為關聯,它們所有的行為都提交到一個統一的地方(假設它是AccountSystem)去處理。這麼做有幾個好處:
l 各大區不需要關心還有哪些其它的大區,它只直接和AccountSystem對話。
l 只需要調整AccountSystem就能調整各大區之間的交互行為,比如我們僅僅希望A和B大區共享信息、C和D大區共享信息,那麼對於這種交互策略的改變也需要修改AccountSystem。
l 有利於大區的擴充,有了新的大區後,我們不用在大區中考慮它的交互行為,統一交給AccountSystem去安排。
現在,再來看看引入AccountSystem後的通訊:
l 網站調用AccountSystem的注冊方法(1)
l AccountSystem調用A、B和C大區的注冊方法(2)
l A、B和C大區的充值方法調用AccountSystem的充值方法(3)
l A、B和C大區的消費方法調用AccountSystem的充值方法(4)
l AccountSystem的充值方法調用A、B和C大區的專有充值方法(只針對本大區的充值)(5)
l AccountSystem的充值方法調用A、B和C大區的專有消費方法(只針對本大區的消費)(6)
至此,你已經實現了中介者模式。你可能會覺得,(1)和(2)非常類似門面模式,沒錯,它確實就是門面模式,而有了(3)~(6)的行為,AccountSystem也就是一個中介者的角色了。
示例代碼
using System;
using System.Collections.Generic;
using System.Text;
namespace MediatorExample
{
class Program
{
static void Main(string[] args)
{
AccountSystem accountSystem = new AccountSystem();
GameSystem gameArea1 = new GameArea1(accountSystem);
GameSystem gameArea2 = new GameArea2(accountSystem);
accountSystem.RegisterGameArea(gameArea1);
accountSystem.RegisterGameArea(gameArea2);
string userName = "aaa";
accountSystem.CreateAccount(userName);
gameArea1.Recharge(userName, 200);
gameArea2.Consume(userName, 50);
accountSystem.QueryBalance(userName);
}
}
class AccountSystem
{
private Dictionary<string, int> userBalance = new Dictionary<string, int>();
private List<GameSystem> gameAreaList = new List<GameSystem>();
public void RegisterGameArea(GameSystem gs)
{
gameAreaList.Add(gs);
}
public void CreateAccount(string userName)
{
userBalance.Add(userName, 0);
foreach (GameSystem gs in gameAreaList)
gs.CreateAccountSelf(userName);
}
public void Recharge(string userName, int amount)
{
if (userBalance.ContainsKey(userName))
{
bool ok = true;
foreach (GameSystem gs in gameAreaList)
ok = gs.RechargeSelf(userName, amount);
if (ok)
userBalance[userName] += amount;
}
}
public void Consume(string userName, int amount)
{
if (userBalance.ContainsKey(userName))
{
bool ok = true;
foreach (GameSystem gs in gameAreaList)
ok = gs.ConsumeSelf(userName, amount);
if (ok)
userBalance[userName] -= amount;
}
}
public void QueryBalance(string userName)
{
Console.WriteLine("Your balance is " + userBalance[userName]);
}
}
abstract class GameSystem
{
private AccountSystem accountSystem;
protected Dictionary<string, int> userBalance = new Dictionary<string, int>();
public GameSystem(AccountSystem accountSystem)
{
this.accountSystem = accountSystem;
}
internal virtual bool CreateAccountSelf(string userName)
{
userBalance.Add(userName, 0);
return true;
}
internal virtual bool RechargeSelf(string userName, int amount)
{
if (userBalance.ContainsKey(userName))
userBalance[userName] += amount;
return true;
}
internal virtual bool ConsumeSelf(string userName, int amount)
{
if (userBalance.ContainsKey(userName))
userBalance[userName] -= amount;
return true;
}
public void Recharge(string userName, int amount)
{
accountSystem.Recharge(userName, amount);
}
public void Consume(string userName, int amount)
{
accountSystem.Consume(userName, amount);
}
}
class GameArea1 : GameSystem
{
public GameArea1(AccountSystem accountSystem) : base(accountSystem) { }
internal override bool CreateAccountSelf(string userName)
{
Console.WriteLine(userName + " Registered in GameAre1");
return base.CreateAccountSelf(userName);
}
internal override bool RechargeSelf(string userName, int amount)
{
base.RechargeSelf(userName, amount);
Console.WriteLine(userName + "'s amount in GameArea1 is " + userBalance[userName]);
return true;
}
internal override bool ConsumeSelf(string userName, int amount)
{
base.ConsumeSelf(userName, amount);
Console.WriteLine(userName + "'s amount in GameArea1 is " + userBalance[userName]);
return true;
}
}
class GameArea2 : GameSystem
{
public GameArea2(AccountSystem accountSystem) : base(accountSystem) { }
internal override bool CreateAccountSelf(string userName)
{
Console.WriteLine(userName + " Registered in GameAre2");
return base.CreateAccountSelf(userName);
}
internal override bool RechargeSelf(string userName, int amount)
{
base.RechargeSelf(userName, amount);
Console.WriteLine(userName + "'s amount in GameArea2 is " + userBalance[userName]);
return true;
}
internal override bool ConsumeSelf(string userName, int amount)
{
base.ConsumeSelf(userName, amount);
Console.WriteLine(userName + "'s amount in GameArea2 is " + userBalance[userName]);
return true;
}
}
}
代碼執行結果如下圖:
代碼說明
l AccountSystem是一個中介者角色,它負責各個同事類之間的交互。要使同事對象參與它的管理,就需要在內部維護一個同事對象的列表。
l 我們看到,AccountSystem的注冊、充值和消費方法會遍歷相關的同事對象並且調用它們的專有方法進行操作。在全部操作完成之後,它才會更新自己的賬戶。
l GameSystem是一個抽象同事。充值和消費方法都有兩種。一種是給外部調用的充值和消費方法,一種是給外部調用的,另外一種是給AccountSystem調用的。在對外的方法中,GameSystem僅僅是把這個請求轉發給中介者,它自己不做實質性的操作,而在xxxSelf()方法中才做真正的充值、消費操作。
l GameArea1和GameArea2是具體同事,調用父類構造方法來和中介者關聯。
l 中介者模式的特點就是同事自己意識到它需要和一個中介者關聯,而在實際的操作過程中,它們只是負責和中介者通訊並且接受中介者的請求,而不再和其它同事發生直接的關聯。
何時采用
如果一組接口相對穩定(如果GameArea1和GameArea2的充值方法定義不一樣,那麼AccountSystem就有點暈眩了)的對象之間的依賴關系錯綜復雜,依賴關系難以維護,或者會發生變動可以考慮引入中介者模式。
實現要點
l 在C#中可以適用delegate關聯中介者和各同事之間的交互行為,這樣各同事就不需要直接和中介者進行耦合。
l 中介者模式和觀察者模式的區別是,前者應用於多對多雜亂交互行為的統籌處理,後者應用於一(多)對多關系的靈活定制。對於本例來說,集中處理後還需要分散處理,那麼後半階段的處理過程可以應用觀察者模式。對於前一節的例子來說,如果有多個主體角色和多個觀察者進行多對多通訊的話,也可以應用中介者模式來統籌這個多對多的過程(大家可以自己嘗試修改前一節的實例來應用中介者模式)。
l 中介者模式和門面模式的區別是,前者的各同事類需要依靠中介者進行雙向通訊,應用於子系統之間,而後者的子系統往往不會通過門面去和調用方進行通訊,趨向於單向通訊,應用於子系列和更高層次的系統。本例中就有門面模式和中介者模式的影子。
l 中介者模式往往可以在構架的層次進行應用,有的時候和觀察者模式以及門面模式一起使用,有的時候又會向觀察者模式和門面模式退化。其實在應用模式的過程中不必過多考慮模式的准確定位,如果我們確實從中得以,那麼這個名字就不重要了。
注意事項
l 不是所有的系統都需要應用中介者模式把多對多的關系轉化為多對一對多的。如果各個同事之間本來的關聯就很清晰(沒有交錯關聯),或這種關聯並不復雜,沒有必要應用中介者。
l 在實際的應用過程中,中介者做的控制並不會向本例那樣簡單,它可能包含很多的處理邏輯。如果還伴隨著需求的改變,中介者角色可能會越來越難維護,此時可以考慮對中介者角色或處理行為應用其它的一些設計模式。