[.NET][C#][Design Pattern] - Mediator Pattern (中介者模式)

前言

這篇文章主要介紹Mediator Pattern的定義以及簡單實作
介紹使用Mediator Pattern的好處, 並提供範例
在軟體架構中Mediator Patternc和Repository Pattern都是常用的pattern
通常.NET專案中會結合MediatR套件使用

定義 Mediator Pattern

IG貼文:
Mediator Pattern Post

GitHub連結:
Sample Code

定義一個 Mediator 物件用來封裝一組物件的互動方式
Mediator 藉由避免物件間相互直接的引用,從而降低它們之間的耦合程度,並且可以讓我們獨立地改變這些物件間的互動方式。

下面這兩張圖片可以簡單體現出Mediator Pattern在做的事

沒有使用中介者, 物件彼此之間直接調用

使用中介者, 物件只依賴mediator來與其他物件溝通

可以發現使用中介者模式之後, 物件都只依賴中介者來傳遞訊息, 而不是直接調用彼此, 以此達到解耦。
在現實生活中, 也有類似的例子, 例如開發團隊, 客服團隊, 產銷團隊, 設計團隊, 若彼此之間沒有一個統一的溝通,
那麼各個團隊耦合度很高, 開發跟設計要協調介面, 又要和產銷和客服討論如何贏得如何符合市場, 這樣一來分工太複雜。
所以需要一個產品經理來當中介者, 協助溝通各個團隊。

優缺點

  • 優點
    1. 降低物件之間的耦合性,讓物件容易重複使用。
    2. 物件之間一對多的關聯性變成一對一,提高系統靈活性,也讓整體容易維護及擴充。
  • 缺點 (Trade-off)
    1. 同事類別過多時,中介者責任很大,會使系統提升一定程度的複雜性。

UML與成員

成員 定義
Mdiator 抽象中介者, 定義註冊進入mediator以及轉發的方法
ConcreteMediator 具體中介者, 定義一個集合來管理同事, 所以聚合Colleague
Colleague 抽象同事, 可以保存中介者, 調用內部方法, 所以聚合Mediator
ConcreteColleague 具體同事, 當要跟其他物件溝通時, 利用內部的中介者進行轉發

實作

專案結構

抽象Mediator

Mediator(抽象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using MediatorPattern.colleagues;

namespace MediatorPattern.mediator
{
// 團隊列舉
public enum teamType
{
ENGINEERING, DESIGN, SERVICE, MARKETING
}

// 抽象mediator
public abstract class Mediator
{
public abstract void Register(teamType type, Colleague colleague); //註冊進入mediator
public abstract void Relay(teamType type, string msg); //轉發;傳遞
}
}

PackageManager(具體Mediator)

PackageManager(具體Mediator)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using MediatorPattern.colleagues;

namespace MediatorPattern.mediator
{
// 具體mediator
public class ProductManager : Mediator
{
// 聚合Colleague, 一個儲存colleague物件的dictionary
private Dictionary<teamType, Colleague> colleagues = new Dictionary<teamType, Colleague>();

public override void Register(teamType type, Colleague colleague)
{
colleague.setMediator(this);
this.colleagues.Add(type, colleague);
}

public override void Relay(teamType type, string msg)
{
Colleague toColleague = this.colleagues[type];
toColleague.receive(msg);
}
}
}

抽象Colleague

Colleague(抽象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using MediatorPattern.mediator;

namespace MediatorPattern.colleagues
{
public abstract class Colleague
{
protected Mediator? Mediator { get; private set; }

public void setMediator(Mediator mediator)
{
this.Mediator = mediator;
}

// 接收訊息後如何反應
public abstract void receive(string msg);

// 傳送訊息給特定物件
public abstract void send(teamType type, string msg);
}
}

具體Colleague

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//具體colleague
public class DesignTeam : Colleague
{
public override void receive(string msg)
{
Console.WriteLine("設計團隊收到訊息: " + msg);
}

public override void send(teamType type, string msg)
{
Console.WriteLine("設計團隊發送訊息: "+msg);
if (this.Mediator != null)
{
this.Mediator.Relay(type, msg);
}
}
}

public class EngineeringTeam : Colleague
{
public override void receive(string msg)
{
Console.WriteLine("工程師團隊收到訊息: "+msg);
}

public override void send(teamType type, string msg)
{
Console.WriteLine("工程師團隊發送訊息: "+msg);
if (this.Mediator != null)
{
this.Mediator.Relay(type, msg);
}
}
}

public class MarketingTeam : Colleague
{
public override void receive(string msg)
{
Console.WriteLine("產銷團隊收到訊息: " + msg);
}

public override void send(teamType type, string msg)
{
Console.WriteLine("產銷團隊發送訊息: "+msg);
if (this.Mediator != null)
{
this.Mediator.Relay(type, msg);
}
}
}

public class ServiceTeam : Colleague
{
public override void receive(string msg)
{
Console.WriteLine("客服團隊收到訊息: " + msg);
}

public override void send(teamType type, string msg)
{
Console.WriteLine("客服團隊發送訊息: "+msg);
if (this.Mediator != null)
{
this.Mediator.Relay(type, msg);
}
}
}

Client端使用

Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using MediatorPattern.colleagues;
using MediatorPattern.mediator;

// 具體mediator
ProductManager pm = new ProductManager();

//具體colleague
Colleague ds = new DesignTeam();
Colleague eg = new EngineeringTeam();
Colleague mk = new MarketingTeam();
Colleague sv = new ServiceTeam();

//註冊進入mediator
pm.Register(teamType.DESIGN, ds);
pm.Register(teamType.ENGINEERING, eg);
pm.Register(teamType.MARKETING, mk);
pm.Register(teamType.SERVICE, sv);

//執行
ds.send(teamType.ENGINEERING, "UI設計稿完成");
Console.WriteLine(String.Concat(Enumerable.Repeat("-", 10)));

eg.send(teamType.MARKETING, "軟體開發完成");
Console.WriteLine(String.Concat(Enumerable.Repeat("-", 10)));

Console.ReadKey();

結果

疑問

  1. DI不也是讓物件間解耦嗎?他們有什麼不同?
    1. 應用場景不同:Mediator模式主要用於解耦合多個相互協作的物件,特別是在複雜的互動場景中。DI則主要用於管理和注入依賴關係,以實現鬆耦合和可測試的程式碼。
    2. 解決不同問題:Mediator解決的是物件之間的協作和通信問題,而DI解決的是依賴關係管理的問題。
    3. 實現方式不同:Mediator需要一個中介者來協調物件之間的互動,通常由一個專門的Mediator類別來實現。DI則是一種設計模式,它可以與不同語言和框架一起使用,並不需要特定的中介者類別。

結語

  • 定義:
    定義一個 Mediator 物件用來封裝一組物件的互動方式。Mediator 藉由避免物件間相互直接的引用,從而降低它們之間的耦合程度,並且可以讓我們獨立地改變這些物件間的互動方式。
  • 成員
    • 抽象Mediator: 定義方法, 轉接訊息
    • 具體Mediator: 實作抽象Mediator, Colleague (擁有0~多個Colleague)
    • 抽象Colleague: 定義方法, 聚合抽象Mediator (Colleague擁有Mediator)
    • 具體Colleague: 實作抽象Mediator
  • 優點
    1. 降低物件之間的耦合性,讓物件容易重複使用。
    2. 物件之間一對多的關聯性變成一對一,提高系統靈活性,也讓整體容易維護及擴充。
  • 缺點 (Trade-off)
    1. 同事類別過多時,中介者責任很大,會使系統提升一定程度的複雜性。