18. Mediator


Posted by WayneCheng on 2021-01-25

關於 Mediator 本篇將討論以下幾個問題

1. 關於 Mediator

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Mediator

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

by Gang of Four

  • 定義一個一組物件間互動邏輯的封裝(Mediator)
  • 透過防止物件之間直接互相引用來減低耦合,並且可以獨立修改其互動邏輯

Mediator(中介者)屬於行為型(Behavioral Patterns),當遇到物件間相互存在複雜引用時,可藉由 Mediator 來將物件間互動邏輯抽離,減低物件之間耦合,之後若需要修改互動邏輯時則不只需要修改 Mediator 而不需要動到物件本身。

優點:

  • 符合 單一職責原則(Single Responsibility Principle)
  • 符合 開閉原則(Open Closed Principle)
  • 快速解除物件間耦合

缺點:

  • 過度使用可能會造成 Mediator 演變成 God Class

2. UML

Class 間關聯:

  • Colleague 關聯 Mediator
  • ConcreteMediator 繼承 Mediator
  • ConcreteColleague 繼承 Colleague
  • ConcreteMediator 關聯 ConcreteColleague

Class:

  • Mediator:中介者的抽象方法或介面
  • ConcreteMediator:中介者實作
  • Colleague:包含 Mediator 的抽象方法或介面
  • ConcreteColleague:Colleague 實作,可經由中介者與其他 ConcreteColleague 互動

3. 將 UML 轉為程式碼

中介者的介面

/// <summary>
/// 中介者的介面
/// </summary>
public interface IMediator
{
    void Send(string message, IColleague colleague);
}

中介者實作

/// <summary>
/// 中介者實作
/// </summary>
public class ConcreteMediator : IMediator
{
    public IColleague Colleague1 { get; set; }
    public IColleague Colleague2 { get; set; }

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

包含 Mediator 的介面

/// <summary>
/// 包含 Mediator 的介面
/// </summary>
public interface IColleague
{
    void Send(string message);
    void Notify(string message);
}

Colleague 實作,可經由中介者與其他 ConcreteColleague 互動

/// <summary>
/// Colleague 實作,可經由中介者與其他 ConcreteColleague 互動
/// </summary>
public class ConcreteColleague1 : IColleague
{
    private IMediator _mediator { get; }
    public ConcreteColleague1(IMediator mediator)
    {
        _mediator = mediator;
    }

    public void Send(string message)
    {
        _mediator.Send(message, this);
    }

    public void Notify(string message)
    {
        Console.WriteLine($"Colleague1 gets message: {message}");
    }
}

/// <summary>
/// Colleague 實作,可經由中介者與其他 ConcreteColleague 互動
/// </summary>
public class ConcreteColleague2 : IColleague
{
    private IMediator _mediator { get; }
    public ConcreteColleague2(IMediator mediator)
    {
        _mediator = mediator;
    }

    public void Send(string message)
    {
        _mediator.Send(message, this);
    }

    public void Notify(string message)
    {
        Console.WriteLine($"Colleague2 gets message: {message}");
    }
}
  1. 建立mediator
  2. mediator傳入colleague1 & colleague2
  3. 再將colleague1 & colleague2指定給mediator
  4. colleague1 & colleague2經由mediator互動
static void Main(string[] args)
{
    #region Default

    Default.ConcreteMediator mediator = new Default.ConcreteMediator();

    Default.ConcreteColleague1 colleague1 = new Default.ConcreteColleague1(mediator);
    Default.ConcreteColleague2 colleague2 = new Default.ConcreteColleague2(mediator);

    mediator.Colleague1 = colleague1;
    mediator.Colleague2 = colleague2;

    colleague1.Send("How are you?");
    colleague2.Send("Fine, thanks.");

    Console.ReadLine();

    #endregion
}

執行結果

Colleague2 gets message: How are you?
Colleague1 gets message: Fine, thanks.

4. 情境

我們接到了一個門市取得其他分店庫存的需求

  • 不同店之間要能夠取得對方庫存資訊以互相支援
  • 各分店販售商品不完全相同,故撈取資料參數不同

中介者的介面

/// <summary>
/// 中介者的介面
/// </summary>
public interface IMediator
{
    void GetOtherStoreInStore(IStore store);
}

中介者實作

/// <summary>
/// 中介者實作
/// </summary>
public class Mediator : IMediator
{
    public IStore StoreDaan { get; set; }
    public IStore StoreXinyi { get; set; }

    public void GetOtherStoreInStore(IStore store)
    {
        if (store == StoreDaan)
        {
            StoreXinyi.GetInStore();
        }
        else
        {
            StoreDaan.GetInStore();
        }
    }
}

包含 Mediator 的門市介面

/// <summary>
/// 包含 Mediator 的門市介面
/// </summary>
public interface IStore
{
    void GetInStore();
}

門市間可透過 Mediator 取得其他分店庫存

/// <summary>
/// 大安店實作,透過 Mediator 取得信義店庫存
/// </summary>
public class StoreDaan : IStore
{
    private IMediator _mediator { get; }
    public StoreDaan(IMediator mediator)
    {
        _mediator = mediator;
    }

    public void GetOtherStoreInStore()
    {
        _mediator.GetOtherStoreInStore(this);
    }

    public void GetInStore()
    {
        Console.WriteLine($"大安店 目前庫存 咖啡 100 杯,蛋糕 5 片");
    }
}

/// <summary>
/// 信義店實作,透過 Mediator 取得大安店庫存
/// </summary>
public class StoreXinyi : IStore
{
    private IMediator _mediator { get; }
    public StoreXinyi(IMediator mediator)
    {
        _mediator = mediator;
    }

    public void GetOtherStoreInStore()
    {
        _mediator.GetOtherStoreInStore(this);
    }

    public void GetInStore()
    {
        Console.WriteLine($"信義店 目前庫存 咖啡 10 杯,蛋糕 120 片");
    }
}
  1. 建立mediator
  2. mediator傳入storeDaan & storeXinyi
  3. 再將storeDaan & storeXinyi指定給mediator
  4. 大安店經由mediator取得信義店庫存
static void Main(string[] args)
{
    Situation.Mediator mediator = new Situation.Mediator();

    Situation.StoreDaan storeDaan = new Situation.StoreDaan(mediator);
    Situation.StoreXinyi storeXinyi = new Situation.StoreXinyi(mediator);

    mediator.StoreDaan = storeDaan;
    mediator.StoreXinyi = storeXinyi;

    storeDaan.GetInStore();
    storeDaan.GetOtherStoreInStore();

    Console.ReadLine();
}

執行結果

大安店 目前庫存 咖啡 100 杯,蛋糕 5 片
信義店 目前庫存 咖啡 10 杯,蛋糕 120 片

完整程式碼

GitHub:Behavioral_05_Mediator


總結

Mediator 為了處理物件間的互動,所以必須直接耦合所有物件,雖然在新增物件互動邏輯時不需要修改物件本身,但在物件日漸增多、邏輯日漸複雜之後,Mediator 很容易變成龐大且難以維護的怪物,適時的拆分 & 重構才能避免問題惡化。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

打造後台管理系統的好幫手:Ant Design

打造後台管理系統的好幫手:Ant Design

 HTTP 概念

HTTP 概念

Day03:從迴圈看 bytecode

Day03:從迴圈看 bytecode


Comments