第四部分:行為型模式 - 中介者模式 (Mediator Pattern)
接下來,我們學習中介者模式。這個模式用一個中介對象來封裝一系列的對象交互。中介者使各個對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
- 核心思想:用一個中介對象來封裝一系列的對象交互。中介者使各個對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
中介者模式 (Mediator Pattern)
“用一個中介對象來封裝一系列的對象交互。中介者使各個對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。” (Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.)
想象一個機場的控制塔:
- 飛機 (Colleague Objects):多架飛機在機場附近飛行,準備降落或起飛。
- 控制塔 (Mediator):控制塔負責協調所有飛機的行動。飛機不直接相互通信來決定誰先降落、誰使用哪個跑道。它們都只與控制塔通信。
- 通信 (Interaction):飛機向控制塔報告自己的狀態和意圖(如請求降落),控制塔根據整體情況向飛機發出指令(如允許降落、指定跑道、等待)。
如果沒有控制塔,飛機之間需要復雜的直接通信來避免碰撞和混亂,這將形成一個復雜的網狀通信結構。控制塔將這種網狀結構簡化為星型結構,所有通信都通過中心節點(控制塔)進行。
1. 目的 (Intent)
中介者模式的主要目的:
- 減少對象間的直接依賴:將對象間復雜的網狀依賴關系轉變為星型依賴關系。各個同事對象不再直接相互引用,而是都只引用中介者。
- 集中控制交互邏輯:對象間的交互邏輯被封裝在中介者對象中,使得交互邏輯更易于理解和維護。
- 促進松耦合:同事對象之間松散耦合,它們可以獨立地變化和復用。
- 提高系統的可擴展性:當需要修改或增加交互行為時,通常只需要修改中介者類,或者增加新的中介者類,而不需要修改大量的同事類。
2. 生活中的例子 (Real-world Analogy)
-
聊天室 (Chat Room):
- 用戶 (Colleague Objects):聊天室中的多個用戶。
- 聊天室服務器 (Mediator):用戶發送消息給服務器,服務器再將消息廣播給聊天室中的其他用戶(或特定用戶)。用戶之間不直接點對點發送消息。
-
聯合國安理會 (UN Security Council):
- 國家 (Colleague Objects):各個成員國。
- 安理會 (Mediator):國家之間通過安理會這個平臺進行溝通、協調和決策,而不是所有國家都兩兩直接談判所有事務。
-
GUI應用程序中的對話框 (Dialog Box):
- 各種控件 (Colleague Objects):如按鈕、文本框、列表框、復選框等。
- 對話框本身 (Mediator):對話框負責協調其內部控件之間的交互。例如,當一個列表框的選擇改變時,對話框可能會啟用或禁用某個按鈕,或者更新某個文本框的內容。控件之間不直接相互操作。
3. 結構 (Structure)
中介者模式通常包含以下角色:
- Mediator (中介者接口/抽象類):定義一個接口用于與各個同事對象通信。它通常包含一個方法,供同事對象在自身狀態改變時通知中介者。
- ConcreteMediator (具體中介者):實現 Mediator 接口。它了解并維護所有的具體同事類,并負責協調它們之間的交互。它封裝了同事之間的復雜交互邏輯。
- Colleague (同事接口/抽象類):定義一個接口,包含一個指向中介者對象的引用。每個同事類都知道其中介者對象。
- ConcreteColleague (具體同事類):實現 Colleague 接口。每個具體同事類只知道自己的行為,當需要與其他同事交互時,它會通知中介者,由中介者去協調。
工作流程:
- 客戶端創建具體中介者對象和所有具體同事對象。
- 客戶端將中介者對象設置給每個同事對象,并將每個同事對象注冊到中介者中。
- 當一個同事對象發生變化或需要與其他同事交互時(例如,用戶在GUI中點擊了一個按鈕
ConcreteColleagueA
):- 該同事對象會調用其
changed()
方法,通知中介者 (ConcreteMediator
)。 - 中介者接收到通知后,根據預定義的交互邏輯,可能會調用其他同事對象的方法(例如,中介者調用
ConcreteColleagueB.someOperationB()
)。
- 該同事對象會調用其
- 同事對象之間不直接通信,所有交互都通過中介者進行。
4. 適用場景 (When to Use)
- 當一組對象以定義良好但復雜的方式進行通信,導致了對象之間存在大量相互依賴和直接引用時(網狀結構)。
- 當一個對象引用其他很多對象并且直接與這些對象通信,導致難以復用該對象時。
- 當你想自定義一個分布在多個類中的行為,而又不想生成太多子類時。可以將這些行為提取到中介者中。
- 在GUI設計中,組件之間的復雜交互(例如,一個組件的狀態變化影響其他多個組件)是中介者模式的經典應用場景。
5. 優缺點 (Pros and Cons)
優點:
- 減少了類間的依賴:將對象之間復雜的網狀通信結構簡化為星型結構,降低了同事類之間的耦合度。
- 集中控制交互:將對象間的交互行為封裝在中介者對象中,使得交互邏輯更易于理解、維護和修改。
- 符合迪米特法則:同事類只需要與中介者通信,不需要了解其他同事類。
- 提高了同事類的可復用性:由于同事類之間的耦合降低,它們更容易被復用。
- 提高了系統的靈活性和可擴展性:修改交互行為通常只需要修改中介者,而不需要修改各個同事類。
缺點:
- 中介者可能變得龐大復雜:如果系統中同事對象過多,或者交互邏輯過于復雜,中介者類可能會變得非常龐大,承擔過多的責任,難以維護(演變成上帝類 God Object)。
- 增加了中介者類:引入了額外的中介者類,增加了系統的類數量。
6. 實現方式 (Implementations)
讓我們以一個簡單的聊天室為例。用戶 (User) 是同事,聊天室 (ChatRoom) 是中介者。
同事接口 (ChatUser - Colleague)
// chat_user.go (Colleague interface - can be an abstract struct or interface)
package colleague// Mediator 定義了中介者需要暴露給同事的方法
type Mediator interface {SendMessage(message string, sender User)
}// User 定義了同事需要暴露給中介者的方法,以及同事自身的方法
type User interface {GetName() stringReceiveMessage(message string, senderName string)Send(message string) // 同事通過此方法經由中介者發送消息SetMediator(mediator Mediator)
}
// User.java (Colleague abstract class or interface)
package com.example.colleague;import com.example.mediator.ChatMediator;public abstract class User {protected ChatMediator mediator;protected String name;public User(ChatMediator mediator, String name) {this.mediator = mediator;this.name = name;}public String getName() {return name;}// 同事發送消息,通過中介者public abstract void send(String message);// 同事接收消息public abstract void receive(String message, String senderName);
}
具體同事 (ConcreteUser - ConcreteColleague)
// concrete_user.go
package colleagueimport "fmt"// ConcreteUser 實現了 User 接口
type ConcreteUser struct {name stringmediator Mediator
}func NewConcreteUser(name string) *ConcreteUser {return &ConcreteUser{name: name}
}func (u *ConcreteUser) SetMediator(mediator Mediator) {u.mediator = mediator
}func (u *ConcreteUser) GetName() string {return u.name
}func (u *ConcreteUser) Send(message string) {fmt.Printf("%s sends: %s\n", u.name, message)if u.mediator != nil {u.mediator.SendMessage(message, u) // 通過中介者發送}
}func (u *ConcreteUser) ReceiveMessage(message string, senderName string) {fmt.Printf("%s receives from %s: %s\n", u.name, senderName, message)
}
// ConcreteUser.java (ConcreteColleague)
package com.example.colleague;import com.example.mediator.ChatMediator;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;public class ConcreteUser extends User {public ConcreteUser(ChatMediator mediator, String name) {super(mediator, name);}@Overridepublic void send(String message) {String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));System.out.println(time + " [" + this.name + "] sends: " + message);mediator.sendMessage(message, this);}@Overridepublic void receive(String message, String senderName) {String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));System.out.println(time + " [" + this.name + "] received from [" + senderName + "]: " + message);}
}
中介者接口 (ChatMediator - Mediator)
// chat_mediator.go (Mediator interface - already defined in colleague/chat_user.go for Go)
// For clarity, we can define it in its own package if preferred.
// package mediator
// type ChatMediator interface {
// SendMessage(message string, sender colleague.User)
// AddUser(user colleague.User)
// }
// ChatMediator.java (Mediator interface)
package com.example.mediator;import com.example.colleague.User;public interface ChatMediator {void sendMessage(String message, User sender);void addUser(User user);
}
具體中介者 (ConcreteChatRoom - ConcreteMediator)
// concrete_chat_room.go
package mediator // Assuming this is in a 'mediator' packageimport ("../colleague" // Path to colleague package"fmt"
)// ConcreteChatRoom 實現了 colleague.Mediator 接口
type ConcreteChatRoom struct {users []colleague.User
}func NewConcreteChatRoom() *ConcreteChatRoom {return &ConcreteChatRoom{users: make([]colleague.User, 0),}
}func (cr *ConcreteChatRoom) AddUser(user colleague.User) {fmt.Printf("ChatRoom: %s joined.\n", user.GetName())cr.users = append(cr.users, user)user.SetMediator(cr) // 關鍵:將中介者設置給同事
}// SendMessage 是 colleague.Mediator 接口的實現
func (cr *ConcreteChatRoom) SendMessage(message string, sender colleague.User) {for _, u := range cr.users {// 不把消息發回給發送者自己if u.GetName() != sender.GetName() {u.ReceiveMessage(message, sender.GetName())}}
}
// ConcreteChatMediator.java (ConcreteMediator)
package com.example.mediator;import com.example.colleague.User;
import java.util.ArrayList;
import java.util.List;public class ConcreteChatMediator implements ChatMediator {private List<User> users;public ConcreteChatMediator() {this.users = new ArrayList<>();}@Overridepublic void addUser(User user) {System.out.println("ChatRoom: " + user.getName() + " joined the chat.");this.users.add(user);// In Java, the mediator is typically passed to User's constructor,// so no need for user.setMediator(this) here if done in constructor.}@Overridepublic void sendMessage(String message, User sender) {for (User user : users) {// Don't send the message back to the senderif (user != sender) {user.receive(message, sender.getName());}}}
}
客戶端使用
// main.go (示例用法)
/*
package mainimport ("./colleague""./mediator"
)func main() {chatRoom := mediator.NewConcreteChatRoom()user1 := colleague.NewConcreteUser("Alice")user2 := colleague.NewConcreteUser("Bob")user3 := colleague.NewConcreteUser("Charlie")// 用戶加入聊天室,聊天室會自動設置自己為用戶的中介者chatRoom.AddUser(user1)chatRoom.AddUser(user2)chatRoom.AddUser(user3)fmt.Println("\n--- Chatting Starts ---")user1.Send("Hi everyone!")// Expected:// Alice sends: Hi everyone!// Bob receives from Alice: Hi everyone!// Charlie receives from Alice: Hi everyone!fmt.Println("\n")user2.Send("Hello Alice!")// Expected:// Bob sends: Hello Alice!// Alice receives from Bob: Hello Alice!// Charlie receives from Bob: Hello Alice!
}
*/
// Main.java (示例用法)
/*
package com.example;import com.example.colleague.ConcreteUser;
import com.example.colleague.User;
import com.example.mediator.ChatMediator;
import com.example.mediator.ConcreteChatMediator;public class Main {public static void main(String[] args) {ChatMediator chatRoom = new ConcreteChatMediator();User user1 = new ConcreteUser(chatRoom, "Alice");User user2 = new ConcreteUser(chatRoom, "Bob");User user3 = new ConcreteUser(chatRoom, "Charlie");chatRoom.addUser(user1);chatRoom.addUser(user2);chatRoom.addUser(user3);System.out.println("\n--- Chatting Starts ---");user1.send("Hi everyone!");// Expected output will show timestamps and formatted messagesSystem.out.println(""); // For spacinguser2.send("Hello Alice! How are you?");System.out.println("");user3.send("I'm good, thanks for asking!");}
}
*/
7. 總結
中介者模式通過引入一個中心協調對象(中介者),將系統中原本復雜的對象間網狀交互結構轉變為星型結構。這有效地降低了對象之間的耦合度,使得各個對象(同事)可以獨立變化,同時也使得交互邏輯集中在中介者中,更易于管理和維護。雖然它可能導致中介者自身變得復雜,但在處理多對多對象交互的場景下,中介者模式提供了一種優雅且有效的解決方案,尤其在GUI開發和需要解耦多個協作組件的系統中非常有用。