iT邦幫忙

2022 iThome 鐵人賽

DAY 26
2

大家在日常生活中應該常常用「Line」去聯絡吧!今天要介紹的 Mediator 模式就是 Line 應用的模板,當我們在互相聊天時,發送出去的訊息都會透過 Line 這個中介者去做統一的處理,接著再將訊息發送給要接收訊息的人,如果將它轉換成程式的角度來看,這樣就能夠讓彼此的耦合性降低。

簡單來說,Mediator 將物件行為封裝起來,我們儘量將業務邏輯拆分成各別獨立且依賴性鬆散的元件,避免物件之間直接引用、參照彼此,這樣可以降低系統耦合姓,提升系統程式碼的可維護性。未來如果有遇到多個元件要相互引用的複雜關係時,就能透過 Mediator 來降低元件間溝通的複雜性。

大家可以想想看下面的範例,是不是有點像之前介紹過的其中一種模式的變化版,我們結尾再來討論,先來看範例吧!

Mediator - 定義

用一個中介對象來封裝一系列的對象互動,中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。

https://ithelp.ithome.com.tw/upload/images/20221002/201364438FLLVzRc4q.png

(圖片來源:https://www.baeldung.com/wp-content/uploads/2019/03/mediator.png)

範例 UML

https://ithelp.ithome.com.tw/upload/images/20221002/20136443Kf1pho9tnv.png

Code要點

  • Participant會去擁有一個Chatroom的實例,當要發送訊息時,會去呼叫Chatroom,也就是會將訊息統一先傳給Chatroom做處理,每個Participant也都有Receive方法去實現接收到訊息的處理,當接收到訊息時會透過Chatroom來去觸發這個方法。
  • Chatroom會有一個participants去紀錄哪些人是有被註冊的,也就是有加入這個群組的,所以當Participant要發送訊息後,會先觸發Chatroom裡的Send方法,接著做判斷,如果有在清單中找到要被傳遞訊息的人,就會從清單中取出這位participant,並且呼叫其的Receive方法。

不囉嗦上Code!

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}'");
        }
    }
}
  • 結果

https://ithelp.ithome.com.tw/upload/images/20221002/20136443ieTXUEG2dD.png

簡單的小結

看完今天的範例後,是不是覺得跟我們在第18天介紹的「Observer」模式有點像呢,我們先回顧一下 Observer 模式的UML圖。

https://ithelp.ithome.com.tw/upload/images/20221002/20136443jc7gzvpi33.png

Chatroom的定義其實就是Youtuber,他會提供註冊的功能並且記錄註冊者,同時都會有一個發送通知的功能,會去觸發註冊者的接收方法。主要差異在於,YoutuberNotifyObservers,他的觸發是主動的,因此他不需要去判斷說Subscriber有沒有在註冊清單裡,可以直接對清單裡所有的成員直接做Update,但Chatroom的話就需要先去判斷才能決定能否發送。

Participant的定義其實就是Subscriber,但最大差別在於Participant,它可以是訊息的接收者,也可以是訊息的傳遞者,因此也就多了Send這個方法,可以主動去請求Chatroom將訊息傳遞給接受者,如果接收者有被Chatroom註冊的話。

我們以更高層的角度來看,就可以把Subscriber想像成同時會有兩種腳色的人,我可能同時是一位 Youtuber,也可能是一位訂閱其他 Youtuber 的人,那這時候再想像 Youtuber這個類別變成是一個 Youtube 管理者,由他去做統一的管理,不管今天你有發布新影片要通知其他人,或者是有其他你訂閱的 Youtuber 發新影片要通知你,都是透過這個管理者去做處理。

希望今天的介紹可以讓大家了解到,模式其實不是死板的,我們可以透過靈活的運用以及變化,讓模式去符合我們現在所需要的應用,那各位我們明天見啦~


上一篇
【DAY25】Memento模式 - 無限生命值的秘訣!
下一篇
【DAY27】Chain of Responsibility模式 - 排程的背後原理
系列文
勇闖秘境!探索物件導向背後的設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言