大家在日常生活中應該常常用「Line」去聯絡吧!今天要介紹的 Mediator 模式就是 Line 應用的模板,當我們在互相聊天時,發送出去的訊息都會透過 Line 這個中介者去做統一的處理,接著再將訊息發送給要接收訊息的人,如果將它轉換成程式的角度來看,這樣就能夠讓彼此的耦合性降低。
簡單來說,Mediator 將物件行為封裝起來,我們儘量將業務邏輯拆分成各別獨立且依賴性鬆散的元件,避免物件之間直接引用、參照彼此,這樣可以降低系統耦合姓,提升系統程式碼的可維護性。未來如果有遇到多個元件要相互引用的複雜關係時,就能透過 Mediator 來降低元件間溝通的複雜性。
大家可以想想看下面的範例,是不是有點像之前介紹過的其中一種模式的變化版,我們結尾再來討論,先來看範例吧!
用一個中介對象來封裝一系列的對象互動,中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
(圖片來源:https://www.baeldung.com/wp-content/uploads/2019/03/mediator.png)
Participant
會去擁有一個Chatroom
的實例,當要發送訊息時,會去呼叫Chatroom
,也就是會將訊息統一先傳給Chatroom
做處理,每個Participant
也都有Receive
方法去實現接收到訊息的處理,當接收到訊息時會透過Chatroom
來去觸發這個方法。Chatroom
會有一個participants
去紀錄哪些人是有被註冊的,也就是有加入這個群組的,所以當Participant
要發送訊息後,會先觸發Chatroom
裡的Send
方法,接著做判斷,如果有在清單中找到要被傳遞訊息的人,就會從清單中取出這位participant
,並且呼叫其的Receive
方法。using System;
using System.Collections.Generic;
namespace DAY26_Mediator
{
internal class Program
{
static void Main(string[] args)
{
Participant Howard = new Participant("Howard");
Participant Paul = new Participant("Paul");
Participant Lisa = new Participant("Lisa");
// 建立私人聊天室
Chatroom privateChatroom = new Chatroom(nameof(privateChatroom));
privateChatroom.Register(Howard);
privateChatroom.Register(Lisa);
Howard.Send("Lisa", "Hi Lisa!");
Lisa.Send("Howard", "Hi Howard!");
Paul.Send("Lisa", "Hi Lisa!"); // Paul 無註冊任何聊天室
Console.WriteLine("------------------");
// 建立公開聊天室
Chatroom publicChatroom = new Chatroom(nameof(publicChatroom));
publicChatroom.Register(Paul);
Paul.Send("Lisa", "Hi Lisa!"); // Paul 註冊的聊天室跟 Lisa 不同
Lisa.Send("Paul", "Hi Paul!"); // Lisa 註冊的聊天室跟 Paul 不同
Console.WriteLine("------------------");
publicChatroom.Register(Lisa);
Paul.Send("Lisa", "Hi Lisa!"); // Paul 註冊的聊天室跟 Lisa 相同
Lisa.Send("Paul", "Hi Paul!"); // Lisa 註冊的聊天室跟 Paul 相同
Console.WriteLine("------------------");
Lisa.Send("Howard", "Hi Howard!"); // Lisa 在 publicChatroom 沒找到 Howard
Howard.Send("Lisa", "Hi Lisa!"); // Howard 在 privateChatroom 有找到 Lisa
}
}
// 定義聊天室抽象類別
public abstract class AbstractChatroom
{
public abstract void Register(Participant participant);
public abstract void Send(string from, string to, string message);
}
public class Chatroom : AbstractChatroom
{
private Dictionary<string, Participant> participants = new Dictionary<string, Participant>();
private string _chatRoomName;
public Chatroom(string chatRoomName)
{
_chatRoomName = chatRoomName;
}
// 如果尚未註冊過則註冊,有無註冊過都會將 participant 的聊天室指向這個實體類別 Chatroom
public override void Register(Participant participant)
{
if (!participants.ContainsValue(participant))
{
participants[participant.Name] = participant;
}
participant.Chatroom = this;
}
public override void Send(string from, string to, string message)
{
// 如果訊息接收者也有註冊於這個聊天室,則會發送訊息
Participant participant = participants.GetValueOrDefault(to);
if (participant != null)
{
participant.Receive(from, message);
}
else
{
Console.WriteLine($"{to}未註冊於{_chatRoomName}這個聊天室,{from}無法發送訊息!");
}
}
}
public class Participant
{
private Chatroom _chatroom;
private string _name;
public Participant(string name)
{
_name = name;
}
public string Name { get { return _name; }}
public Chatroom Chatroom
{
set { _chatroom = value; }
get { return _chatroom; }
}
public void Send(string to, string message)
{
if (_chatroom == null)
Console.WriteLine($"{Name} 尚未註冊於任何聊天室");
else
_chatroom.Send(_name, to, message);
}
public virtual void Receive(string from, string message)
{
Console.WriteLine($"{from} 發送給 {Name}: '{message}'");
}
}
}
看完今天的範例後,是不是覺得跟我們在第18天介紹的「Observer」模式有點像呢,我們先回顧一下 Observer 模式的UML圖。
Chatroom
的定義其實就是Youtuber
,他會提供註冊的功能並且記錄註冊者,同時都會有一個發送通知的功能,會去觸發註冊者的接收方法。主要差異在於,Youtuber
的NotifyObservers
,他的觸發是主動的,因此他不需要去判斷說Subscriber
有沒有在註冊清單裡,可以直接對清單裡所有的成員直接做Update
,但Chatroom
的話就需要先去判斷才能決定能否發送。
而Participant
的定義其實就是Subscriber
,但最大差別在於Participant
,它可以是訊息的接收者,也可以是訊息的傳遞者,因此也就多了Send
這個方法,可以主動去請求Chatroom
將訊息傳遞給接受者,如果接收者有被Chatroom
註冊的話。
我們以更高層的角度來看,就可以把Subscriber
想像成同時會有兩種腳色的人,我可能同時是一位 Youtuber,也可能是一位訂閱其他 Youtuber 的人,那這時候再想像 Youtuber
這個類別變成是一個 Youtube 管理者,由他去做統一的管理,不管今天你有發布新影片要通知其他人,或者是有其他你訂閱的 Youtuber 發新影片要通知你,都是透過這個管理者去做處理。
希望今天的介紹可以讓大家了解到,模式其實不是死板的,我們可以透過靈活的運用以及變化,讓模式去符合我們現在所需要的應用,那各位我們明天見啦~