Mediator Pattern(中介者模式)
是什麼?
Mediator Pattern 定義一個中介物件來封裝一組物件之間的互動。各物件不再直接互相引用,而是透過中介者來溝通,降低物件之間的耦合度。
ℹ️GoF 分類
Mediator 屬於行為型模式(Behavioral Pattern),重點在於用一個中心物件來協調多個物件之間的互動。
什麼時候用?
- 多個物件之間有複雜的交互關係,形成網狀結構
- 你想把分散在各物件中的互動邏輯集中管理
- 表單中多個欄位之間有聯動關係(選了 A,B 要跟著變)
- 你需要一個事件調度中心來協調不同模組
什麼時候不該用?
⚠️過度設計警告
Mediator 很容易變成「上帝物件(God Object)」,把所有邏輯都塞進去。如果物件之間只有簡單的一對一關係,直接呼叫就好。當 Mediator 變得過於龐大時,考慮拆分成多個小 Mediator。
執行流程
定義 Mediator 介面
宣告通知方法供 Colleague 呼叫
定義 Colleague 基類
持有 Mediator 參考,事件發生時通知 Mediator
實作具體 Mediator
接收通知後,協調相關 Colleague 的反應
實作具體 Colleague
只跟 Mediator 溝通,不直接引用其他 Colleague
Client 組裝
建立 Mediator 和 Colleague,註冊關係
流程解讀:Mediator 把「多對多通訊」改造成「多對一加一對多」。每個 Colleague 只持有 Mediator 的參考,有事件時通知 Mediator。Mediator 知道所有 Colleague,收到通知後根據業務邏輯決定通知哪些 Colleague、以什麼順序通知。這樣 Colleague 之間完全解耦,互動邏輯集中在 Mediator 中管理。
程式碼範例
C# 版本
// 1. Mediator 介面
public interface IChatRoom
{
void SendMessage(string message, User sender);
void AddUser(User user);
}
// 2. Colleague 基類
public abstract class User
{
protected IChatRoom ChatRoom;
public string Name { get; }
protected User(string name, IChatRoom chatRoom)
{
Name = name;
ChatRoom = chatRoom;
chatRoom.AddUser(this);
}
public void Send(string message) => ChatRoom.SendMessage(message, this);
public abstract void Receive(string message, string fromUser);
}
// 3. 具體 Colleague
public class ChatUser : User
{
public ChatUser(string name, IChatRoom chatRoom) : base(name, chatRoom) { }
public override void Receive(string message, string fromUser)
{
Console.WriteLine($" [{Name}] 收到來自 {fromUser} 的訊息:{message}");
}
}
// 4. 具體 Mediator
public class ChatRoom : IChatRoom
{
private readonly List<User> _users = new();
public void AddUser(User user)
{
_users.Add(user);
Console.WriteLine($"[聊天室] {user.Name} 加入了聊天室");
}
public void SendMessage(string message, User sender)
{
Console.WriteLine($"[聊天室] {sender.Name} 說:{message}");
foreach (var user in _users)
{
if (user != sender)
user.Receive(message, sender.Name);
}
}
}
// 5. 使用
var chatRoom = new ChatRoom();
var alice = new ChatUser("Alice", chatRoom);
var bob = new ChatUser("Bob", chatRoom);
var charlie = new ChatUser("Charlie", chatRoom);
alice.Send("大家好!");
// Bob 和 Charlie 收到訊息
bob.Send("嗨 Alice!");
// Alice 和 Charlie 收到訊息TypeScript 版本
// 1. Mediator 介面
interface ChatRoom {
sendMessage(message: string, sender: User): void;
addUser(user: User): void;
}
// 2. Colleague
class User {
constructor(public name: string, private chatRoom: ChatRoom) {
chatRoom.addUser(this);
}
send(message: string) { this.chatRoom.sendMessage(message, this); }
receive(message: string, fromUser: string) {
console.log(` [${this.name}] 收到來自 ${fromUser} 的訊息:${message}`);
}
}
// 3. 具體 Mediator
class ConcreteChatRoom implements ChatRoom {
private users: User[] = [];
addUser(user: User) {
this.users.push(user);
console.log(`[聊天室] ${user.name} 加入了聊天室`);
}
sendMessage(message: string, sender: User) {
console.log(`[聊天室] ${sender.name} 說:${message}`);
this.users
.filter(u => u !== sender)
.forEach(u => u.receive(message, sender.name));
}
}
// 4. 使用
const chatRoom = new ConcreteChatRoom();
const alice = new User("Alice", chatRoom);
const bob = new User("Bob", chatRoom);
const charlie = new User("Charlie", chatRoom);
alice.send("大家好!");
bob.send("嗨 Alice!");Python 版本
from abc import ABC, abstractmethod
# 1. Mediator 介面
class ChatRoom(ABC):
@abstractmethod
def send_message(self, message: str, sender: "User") -> None:
pass
@abstractmethod
def add_user(self, user: "User") -> None:
pass
# 2. Colleague
class User:
def __init__(self, name: str, chat_room: ChatRoom):
self.name = name
self._chat_room = chat_room
chat_room.add_user(self)
def send(self, message: str) -> None:
self._chat_room.send_message(message, self)
def receive(self, message: str, from_user: str) -> None:
print(f" [{self.name}] 收到來自 {from_user} 的訊息:{message}")
# 3. 具體 Mediator
class ConcreteChatRoom(ChatRoom):
def __init__(self):
self._users: list[User] = []
def add_user(self, user: User) -> None:
self._users.append(user)
print(f"[聊天室] {user.name} 加入了聊天室")
def send_message(self, message: str, sender: User) -> None:
print(f"[聊天室] {sender.name} 說:{message}")
for user in self._users:
if user != sender:
user.receive(message, sender.name)
# 4. 使用
chat_room = ConcreteChatRoom()
alice = User("Alice", chat_room)
bob = User("Bob", chat_room)
charlie = User("Charlie", chat_room)
alice.send("大家好!")
bob.send("嗨 Alice!")Java 版本
import java.util.ArrayList;
import java.util.List;
// 1. Mediator 介面
public interface ChatRoom {
void sendMessage(String message, User sender);
void addUser(User user);
}
// 2. Colleague
public class User {
private final String name;
private final ChatRoom chatRoom;
public User(String name, ChatRoom chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
chatRoom.addUser(this);
}
public String getName() { return name; }
public void send(String message) {
chatRoom.sendMessage(message, this);
}
public void receive(String message, String fromUser) {
System.out.println(" [" + name + "] 收到來自 " + fromUser + " 的訊息:" + message);
}
}
// 3. 具體 Mediator
public class ConcreteChatRoom implements ChatRoom {
private final List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
System.out.println("[聊天室] " + user.getName() + " 加入了聊天室");
}
public void sendMessage(String message, User sender) {
System.out.println("[聊天室] " + sender.getName() + " 說:" + message);
for (User user : users) {
if (user != sender) {
user.receive(message, sender.getName());
}
}
}
}
// 4. 使用
ChatRoom chatRoom = new ConcreteChatRoom();
User alice = new User("Alice", chatRoom);
User bob = new User("Bob", chatRoom);
User charlie = new User("Charlie", chatRoom);
alice.send("大家好!");
bob.send("嗨 Alice!");結構圖
結構解讀:三個 Colleague 互相不認識,全部只跟 Mediator 溝通。如果沒有 Mediator,3 個物件之間有 3 條直接連線(N 個物件有 N*(N-1)/2 條)。有了 Mediator,每個物件只有 1 條連線到 Mediator,從網狀依賴變成星狀依賴。Mediator 知道所有 Colleague,收到訊息後根據邏輯決定轉發給誰。
實戰補充
💡資深開發者經驗
MediatR 套件:.NET 生態系的 MediatR 是 Mediator Pattern 的熱門實作,搭配 CQRS 把 Command/Query 的分發解耦。IRequest + IRequestHandler 讓 Controller 只需 _mediator.Send(command) 就好。
表單欄位聯動:前端的複雜表單(選了國家就更新城市選項、勾了某選項就停用另一個欄位)用 Mediator 集中管理聯動邏輯,避免欄位之間互相引用形成義大利麵。
防止 God Object:Mediator 最大的風險是變成 God Object。解法:按業務功能拆分成多個小 Mediator,或搭配 Pipeline Behavior(MediatR 的做法)把橫切關注點(Log、Validation)抽出。
Mediator vs Observer:Observer 是廣播(一對多通知),Mediator 是協調(多對多互動的中心化管理)。Observer 的 Subject 不管 Observer 怎麼反應,Mediator 會根據事件來協調各方行為。
理解測驗
🤔 Mediator Pattern 解決的核心問題是什麼?
🤔 Mediator Pattern 最常見的風險是什麼?
🤔 以下哪個場景最適合用 Mediator Pattern?
面試常見問題
Q: Mediator 和 Observer 都涉及物件之間的溝通,怎麼區分?
A: Observer 是「廣播」— Subject 通知所有 Observer,Subject 不關心 Observer 怎麼反應。Mediator 是「協調」— Mediator 收到事件後,根據業務邏輯決定通知誰、怎麼反應。Observer 的 Subject 和 Observer 是一對多的單向關係,Mediator 的 Colleague 之間是多對多的雙向關係(透過 Mediator 中轉)。
Q: MediatR 在 .NET 中的典型使用方式是什麼?
A: MediatR 把 Controller 和 Handler 解耦。Controller 只做一件事:await _mediator.Send(new CreateOrderCommand(...)) 。MediatR 自動找到對應的 IRequestHandler 來處理。搭配 Pipeline Behavior 可以在 Handler 前後加入日誌、驗證、交易管理等橫切關注點(Cross-Cutting Concerns,指跨越多個模組的共通功能如日誌、授權)。
相關模式
| 模式 | 關係 | |------|------| | Observer | Observer 是單向廣播,Mediator 是雙向協調。當 Observer 的通知鏈變得太複雜時,考慮改用 Mediator | | Facade | 都是中心化的協調者,但 Facade 是單向的(簡化子系統存取),Mediator 是雙向的(管理同事間互動) | | Command | MediatR 就是 Mediator + Command 的結合 — Command 封裝請求,Mediator 負責分發 | | Chain of Responsibility | 都能處理請求分發,但 Mediator 是集中式(一個中心),CoR 是分散式(一條鏈) |
重點整理
💡一句話記住
Mediator Pattern = 機場塔台:所有人只跟塔台說話,塔台決定誰該動。 口訣:「網狀變星狀,塔台管全場」
| 概念 | 說明 | |------|------| | Mediator(中介者介面) | 定義物件之間溝通的介面 | | ConcreteMediator | 實作協調邏輯,知道所有 Colleague | | Colleague(同事) | 只跟 Mediator 溝通,不直接引用其他 Colleague | | 核心好處 | 把網狀依賴簡化為星狀,降低耦合度 | | 代價 | Mediator 可能變成 God Object |