阿猩的公司在新專案導入RabbitMQ,在送Message至Queue時,使用了Mediator Pattern中介者模式,今天要練習的設計模式就是中介者模式。阿猩的公司在新專案導入RabbitMQ,在送Message至Queue時,使用了Mediator Pattern中介者模式,今天要練習的設計模式就是中介者模式。
情境說明
假設目前要開發一套問題單處理機制,根據不同團隊發出的請求,送給不同團隊做後續處理。
釐清業務需求
老樣子,阿猩先清點此次範例要完成的事情。
例如
- 定義中介者需要做的事情,中介者需要知道有哪些團隊,阿猩在此範例以abstract表示
- 建立中介者物件,例如PM,並實作中介者,
- 定義團隊需要做的事情,團隊需要做的事情為發送請求、及接收請求,阿猩在此範例以abstract表示
- 建立團隊物件,例如前端團隊、後端團隊,並實作團隊要做的事情
- 建立團隊處理問題單的物件
依職責建立物件
定義中介者需要做的事情
中介者至少需要完成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();
}
}
中介者模式思考重點
- Mediator,須知道所有Colleague,並知道Request要轉發給誰。
- Colleague,須可以送Requsest給Mediator,並接收從Mediator的Response。
- 實際處理問題的程式,會出現在收到中介者通知之後,根據Event的Type,以對應的EventHandler做處理