引子:世界需要和平#

伊拉克接連發生爆炸事件、巴以問題、伊朗核問題、朝鮮核問題……國與國之間關係複雜,戰略盟友、戰略對手、利益相關者……各國政府要花大量人力物力在政治、經濟、外交上維繫關係。

第二次世界大戰前,世界上沒有一個民主中立的協調組織,於是出現法西斯聯盟,造成人類史上最大災難。

1945 年成立聯合國之後,地球上再沒有發生世界範圍的大型戰爭——聯合國正是國際間的中介者

緊耦合的世界#

對應到軟體:

  • 物件之間需要知道其他所有物件 → 把系統分割成多個物件本來能增加複用性
  • 物件間相互關聯的激增反而降低複用性
  • 大量連接讓物件離開其他物件就無法工作 → 系統表現為不可分割的整體,難以改動

解法呼應第 11 章的迪米特法則:兩個不必直接通訊的類,不應發生直接的相互作用;可以透過第三者轉發呼叫。

中介者模式#

中介者模式(Mediator Pattern),又稱調停者模式:用一個中介物件來封裝一系列的物件交互。中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。[DP]

透過中介者,可以將系統的網狀結構變成以中介者為中心的星形結構

flowchart LR
    subgraph 網狀[網狀結構:物件互相耦合]
        A1((中)) --- A2((美))
        A1 --- A3((俄))
        A1 --- A4((英))
        A2 --- A3
        A2 --- A4
        A3 --- A4
    end
    subgraph 星形[星形結構:透過中介者]
        M((聯合國))
        M --- B1((中))
        M --- B2((美))
        M --- B3((俄))
        M --- B4((英))
    end

結構#

  • Mediator(抽象中介者):定義同事物件到中介者物件的介面
  • ConcreteMediator(具體中介者):實作抽象方法;需要知道所有具體同事類,並從具體同事接收訊息、向具體同事發出命令
  • Colleague(抽象同事類)
  • ConcreteColleague(具體同事類):每個具體同事只知道自己的行為,不了解其他同事;但都認識中介者
classDiagram
    class Mediator {
        <<abstract>>
        +Send(string, Colleague)*
    }
    class ConcreteMediator
    class Colleague {
        <<abstract>>
        #Mediator mediator
    }
    class ConcreteColleague1
    class ConcreteColleague2
    Mediator <|-- ConcreteMediator
    Colleague <|-- ConcreteColleague1
    Colleague <|-- ConcreteColleague2
    Colleague o--> Mediator
    ConcreteMediator --> ConcreteColleague1
    ConcreteMediator --> ConcreteColleague2
abstract class Mediator
{
    public abstract void Send(string message, Colleague colleague);
}

abstract class Colleague
{
    protected Mediator mediator;
    public Colleague(Mediator mediator) { this.mediator = mediator; }
}

class ConcreteMediator : Mediator
{
    public ConcreteColleague1 Colleague1 { set; private get; }
    public ConcreteColleague2 Colleague2 { set; private get; }

    public override void Send(string message, Colleague colleague)
    {
        if (colleague == Colleague1) Colleague2.Notify(message);
        else                          Colleague1.Notify(message);
    }
}

class ConcreteColleague1 : Colleague
{
    public ConcreteColleague1(Mediator m) : base(m) { }
    public void Send(string message) => mediator.Send(message, this);
    public void Notify(string message) => Console.WriteLine("同事 1 得到訊息:" + message);
}

範例:聯合國安理會#

abstract class UnitedNations
{
    public abstract void Declare(string message, Country colleague);
}

abstract class Country
{
    protected UnitedNations mediator;
    public Country(UnitedNations m) { mediator = m; }
}

class USA : Country
{
    public USA(UnitedNations m) : base(m) { }
    public void Declare(string message)  => mediator.Declare(message, this);
    public void GetMessage(string message) => Console.WriteLine("美國獲得對方訊息:" + message);
}

class Iraq : Country
{
    public Iraq(UnitedNations m) : base(m) { }
    public void Declare(string message)  => mediator.Declare(message, this);
    public void GetMessage(string message) => Console.WriteLine("伊拉克獲得對方訊息:" + message);
}

class UnitedNationsSecurityCouncil : UnitedNations
{
    public USA  Colleague1 { set; private get; }
    public Iraq Colleague2 { set; private get; }

    public override void Declare(string message, Country colleague)
    {
        if (colleague == Colleague1) Colleague2.GetMessage(message);
        else                          Colleague1.GetMessage(message);
    }
}

客戶端:

UnitedNationsSecurityCouncil UNSC = new UnitedNationsSecurityCouncil();
USA  c1 = new USA(UNSC);
Iraq c2 = new Iraq(UNSC);
UNSC.Colleague1 = c1;
UNSC.Colleague2 = c2;

c1.Declare("不准研製核武器,否則要發動戰爭!");
c2.Declare("我們沒有核武器,也不怕侵略。");

Mediator vs. ConcreteMediator#

若不存在擴展情況,MediatorConcreteMediator 可合二為一。

若聯合國未來還會擴展(如國際勞工組織、教科文組織、世界貿易組織),則拆開為抽象中介者 + 具體中介者比較合適。

中介者模式的優缺點#

優點#

  • 減少 Colleague 之間的依賴——可獨立改變和複用各個 Colleague 與 Mediator
  • 把對象如何協作進行了抽象,站在更宏觀的角度看待系統
    • 巴以衝突原本只是國與國的矛盾,看法狹隘
    • 站在聯合國安理會的角度,能從全球化、更客觀地看問題

缺點#

具體中介者 ConcreteMediator 可能因為 ConcreteColleague 越來越多而變得非常複雜,反而難以維護。

中介者控制集中化,把「交互的複雜性」轉變為「中介者的複雜性」——它會變得比任何一個同事都複雜。

聯合國秘書長就是「全球最大的官」——優點來自集中控制,缺點也是。

你一直在用而不自知#

WinForm 的 Form 或 ASP.NET 的 aspx 頁面,就是典型的中介者

計算器上的按鈕、文字框、選單都被各自類別封裝;按下「數字按鈕」要在文字框顯示數字,並非直接呼叫 TextBox,而是觸發事件——所有控件的交互都由 Form 窗體的程式碼作為中介來操作。

中介者模式適合「一組物件以定義良好但是複雜的方式進行通訊」的場合,以及想客製一個分布在多個類中的行為而又不想生成太多子類的場合。