【Design Pattern】中介者模式

阿猩的公司在新專案導入RabbitMQ,在送Message至Queue時,使用了Mediator Pattern中介者模式,今天要練習的設計模式就是中介者模式。阿猩的公司在新專案導入RabbitMQ,在送Message至Queue時,使用了Mediator Pattern中介者模式,今天要練習的設計模式就是中介者模式

 

情境說明



假設目前要開發一套問題單處理機制,根據不同團隊發出的請求,送給不同團隊做後續處理。


釐清業務需求
老樣子,阿猩先清點此次範例要完成的事情。

例如

  1. 定義中介者需要做的事情,中介者需要知道有哪些團隊,阿猩在此範例以abstract表示
  2. 建立中介者物件,例如PM,並實作中介者,
  3. 定義團隊需要做的事情,團隊需要做的事情為發送請求、及接收請求,阿猩在此範例以abstract表示
  4. 建立團隊物件,例如前端團隊、後端團隊,並實作團隊要做的事情
  5. 建立團隊處理問題單的物件

 

 

依職責建立物件


定義中介者需要做的事情
中介者至少需要完成2項工作,一是知道目前有哪些團隊,二是根據使用者提出的問題單,轉發需求至其他團隊,請其他團隊接著處理。

/// <summary>
/// 註冊目前團隊
/// </summary>
/// <param name="type"></param>
/// <param name="colleague"></param>
public abstract void Register(Type type, Colleague colleague);

/// <summary>
/// 轉發請求至適當的團隊
/// </summary>
/// <param name="type"></param>
/// <param name="msg"></param>
public abstract void Relay(Type type, string questionNo, string msg);

 

建立中介者物件,並實作中介者
中介者要將問題單轉發給哪個團隊,阿猩範例的邏輯是,將Type傳給Mediator,換句話說,也就是發出請求的團隊,直接告訴中介者,這個問題單要轉發給哪個團隊,但判斷要讓哪個團隊接手處理的程式,應該也可以寫在中介者實作中。

另外,colleagues的目的是為了要讓中介者,記得目前已有的團隊。

class ProductManager : Mediator
{
    private Dictionary<Type, Colleague> colleagues = new Dictionary<Type, Colleague>();

    /// <summary>
    /// 註冊目前團隊
    /// </summary>
    /// <param name="type"></param>
    /// <param name="colleague"></param>
    public override void Register(Type type, Colleague colleague)
    {
       colleague.setMediator(this);
       colleagues.Add(type, colleague);
    }

    /// <summary>
    /// 轉發請求至適當的團隊
    /// </summary>
    /// <param name="type"></param>
    /// <param name="msg"></param>
    public override void Relay(Type type, string questionNo, string msg)
    {
       Colleague target = colleagues[type];
       target.receive(msg);
    }
}

 

定義團隊需要做的事情
團隊至少需要完成2項工作,一是發送請求,二是接收請求並做後續處理。

abstract class Colleague
{
    protected Mediator _mediator;
    public void setMediator(Mediator mediator)
    {
       _mediator = mediator;
    }

    public abstract void Receive(string QuestionNo, string msg);
    public abstract void Send(Type type, string QuestionNo, string msg);    }

 

建立團隊物件,並實作團隊要做的事情範例流程撰寫
建立團隊物件後,發送請求並不需要自己與其他團隊溝通,只要將需求發給PM,由PM轉交給其他團隊處理。任何團隊也必須要有Receive,接收由PM轉發過來的請求,並做後續處理。

class FrontEndTeam : Colleague
{
    const string teamName = "前端團隊";

    public override void Receive(string questionNo, string msg)
    {
       Console.WriteLine($"{teamName}收到問題單,單號: {questionNo},請求內容: {msg}");

       //做後續處理
       FrontTeamHandler handler = new FrontTeamHandler (teamName, msg);
       handler.Handle();
    }

    public override void Send(Type type, string questionNo, string msg)
    {
       Console.WriteLine($"{teamName}發出問題單,單號: {questionNo},請求內容: {msg}");
       _mediator.Relay(type, questionNo, msg);
    }    
}

class BackEndTeam : Colleague
{
    const string teamName = "後端團隊";

    public override void Receive(string questionNo, string msg)
    {
       Console.WriteLine($"{teamName}收到問題單,單號: {questionNo},請求內容: {msg}");

       //做後續處理
       BackEndTeamHandler handler = new BackEndTeamHandler(teamName, msg);
       handler.Handle();
    }

    public override void Send(Type type, string questionNo, string msg)
    {
       Console.WriteLine($"{teamName}發出問題單,單號: {questionNo},請求內容: {msg}");
       _mediator.Relay(type, questionNo, msg);
    }    
}

 

建立團隊處理問題單的物件
實務上來說,不可能馬上收到問題單就自動處理完,應該會將問題單存到DB,或是像阿猩公司專案,是為了處理Message,要寫進RabbitMQ的Quere,待後續團隊收到通知後,並請人處理問題單,完成後會修改問題單記號,才能發出已修正的通知。

這裡的範例,只是想要特別呈現,用EventHandler的方式,進行各式各樣的處理。

class BackEndTeamHandler
{
    private string _teamName = string.Empty;
    private string _msg = string.Empty;
    public BackEndTeamHandler(string teamName, string msg)
    {
       _teamName = teamName;
        _msg = msg;
    }

    /// <summary>
    /// 處理常式
    /// </summary>
    /// <returns></returns>
    public void Handle()
    {
        try
        {
           Console.WriteLine($"{_teamName} 已處理 「 {_msg} 」的問題");
        }
        catch (System.Exception e)
        {
            throw e;
        }
    }
}

 

Program.cs的內容

class Program
{
    static void Main(string[] args)
    {
       // 1、new mediator
       ProductManager PM = new ProductManager();
    
       // 2、new Colleague
       Colleague FrontEndTeam = new FrontEndTeam();
       Colleague BackEndTeam = new BackEndTeam();
       Colleague QATeam = new QATeam();
       Colleague CustomerTeam = new CustomerTeam();
           
       // 3、中介者註冊
       PM.Register(FrontEndTeam.GetType(), FrontEndTeam);
       PM.Register(BackEndTeam.GetType(), BackEndTeam);
       PM.Register(QATeam.GetType(), QATeam);
       PM.Register(CustomerTeam.GetType(), CustomerTeam);
      
       // 4、發問題單
       BackEndTeam.Send(QATeam.GetType(), "101".PadLeft(8 , '0'), "QA測試方法有問題");
       Console.ReadLine();
       FrontEndTeam.Send(BackEndTeam.GetType(), "102".PadLeft(8, '0'), "目前API會回覆Status:500");
       Console.ReadLine();
   }
}

 

 

中介者模式思考重點


  1. Mediator,須知道所有Colleague,並知道Request要轉發給誰。
  2. Colleague,須可以送Requsest給Mediator,並接收從Mediator的Response。
  3. 實際處理問題的程式,會出現在收到中介者通知之後,根據Event的Type,以對應的EventHandler做處理