Mediator
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”Mediator เพิ่ม object ที่ห่อหุ้มวิธีที่ object กลุ่มหนึ่งโต้ตอบกัน แทนที่แต่ละตัวจะถือ reference ไปยังตัวอื่นครบทุกตัว ทุกตัวจะคุยกับ mediator ตัวเดียว แล้ว mediator เป็นคนตัดสินว่าใครต้องรู้อะไร
เมื่อมี object หลายตัวต้องประสานงานกัน วิธีที่ตรงไปตรงมาที่สุดคือต่อสายเข้าหากันตรง ๆ ในห้องแชต ผู้เข้าร่วมแต่ละคนต้องเก็บรายชื่อผู้เข้าร่วมคนอื่นครบทุกคน ในฟอร์ม dialog ปุ่ม submit อ้างถึงช่องข้อความ ช่องข้อความอ้างถึง checkbox และ checkbox อ้างถึง label ต่อกันเป็นทอด ๆ จำนวนการเชื่อมต่อจึงโตแบบกำลังสองตามจำนวน object แถม object แต่ละตัวยังนำกลับมาใช้ซ้ำได้ยาก เพราะลากผู้ทำงานร่วมทั้งพวงติดมาด้วย
Mediator ยุบสายใยนั้นให้กลายเป็นแบบ hub and spokes เพื่อนร่วมงานแต่ละตัวรู้จักแค่ mediator พอมีอะไรเกิดขึ้นก็แจ้ง mediator แล้ว mediator ค่อยส่งผลที่ตามมาต่อไปยังเพื่อนร่วมงานตัวที่สนใจ เพื่อนร่วมงานจึงเรียบง่ายลงและนำกลับมาใช้ซ้ำได้อิสระ ส่วนตรรกะการโต้ตอบก็มารวมอยู่ที่เดียวให้อ่านและแก้ได้ง่าย ข้อแลกเปลี่ยนคือตัว mediator เองอาจบวมขึ้นเรื่อย ๆ ถ้าปล่อยให้ดูดซับทุกอย่างเข้าไปมากเกินไป
โครงสร้าง
หัวข้อที่มีชื่อว่า “โครงสร้าง”classDiagram
class Mediator {
<<interface>>
+notify(sender, event)
}
class ChatRoom {
-members: List~Colleague~
+register(c Colleague)
+notify(sender, event)
}
class Colleague {
-mediator Mediator
+send(msg)
+receive(from, msg)
}
Mediator <|.. ChatRoom
ChatRoom o-- Colleague : coordinates
Colleague --> Mediator : talks to - Mediator — interface ของฮับ เพื่อนร่วมงานเรียกใช้เมื่อมีอะไรเกิดขึ้น
- ChatRoom — concrete mediator ถือเพื่อนร่วมงานทั้งหมดไว้ และบรรจุตรรกะกำหนดเส้นทางของ event
- Colleague — ผู้เข้าร่วม รู้จักแต่ mediator ไม่รู้จักเพื่อนร่วมงานตัวอื่น จึงแยกขาดจากกันได้
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”ห้องแชตในฐานะ mediator ผู้ใช้แต่ละคนส่งผ่านห้อง และห้องจะกระจายข้อความไปยังผู้ใช้คนอื่นทุกคน — ไม่มีผู้ใช้คนใดอ้างถึงผู้ใช้อีกคนโดยตรงเลย
interface ChatMediator { register(user: User): void; broadcast(from: User, message: string): void;}
class ChatRoom implements ChatMediator { private users: User[] = [];
register(user: User): void { this.users.push(user); }
broadcast(from: User, message: string): void { for (const user of this.users) { if (user !== from) user.receive(from.name, message); } }}
class User { constructor(readonly name: string, private room: ChatMediator) { room.register(this); }
send(message: string): void { this.room.broadcast(this, message); }
receive(from: string, message: string): void { console.log(`[${this.name}] ${from}: ${message}`); }}
const room = new ChatRoom();const alice = new User('Alice', room);const bob = new User('Bob', room);
alice.send('hi'); // [Bob] Alice: hibob.send('hello'); // [Alice] Bob: hellofrom __future__ import annotationsfrom abc import ABC, abstractmethod
class ChatMediator(ABC): @abstractmethod def register(self, user: "User") -> None: ...
@abstractmethod def broadcast(self, sender: "User", message: str) -> None: ...
class ChatRoom(ChatMediator): def __init__(self) -> None: self._users: list[User] = []
def register(self, user: "User") -> None: self._users.append(user)
def broadcast(self, sender: "User", message: str) -> None: for user in self._users: if user is not sender: user.receive(sender.name, message)
class User: def __init__(self, name: str, room: ChatMediator) -> None: self.name = name self._room = room room.register(self)
def send(self, message: str) -> None: self._room.broadcast(self, message)
def receive(self, sender: str, message: str) -> None: print(f"[{self.name}] {sender}: {message}")
room = ChatRoom()alice = User("Alice", room)bob = User("Bob", room)
alice.send("hi") # [Bob] Alice: hibob.send("hello") # [Alice] Bob: hellopackage main
import "fmt"
type Mediator interface { Register(u *User) Broadcast(sender *User, message string)}
type ChatRoom struct { users []*User}
func (r *ChatRoom) Register(u *User) { r.users = append(r.users, u)}
func (r *ChatRoom) Broadcast(sender *User, message string) { for _, u := range r.users { if u != sender { u.Receive(sender.name, message) } }}
type User struct { name string room Mediator}
func NewUser(name string, room Mediator) *User { u := &User{name: name, room: room} room.Register(u) return u}
func (u *User) Send(message string) { u.room.Broadcast(u, message)}
func (u *User) Receive(from, message string) { fmt.Printf("[%s] %s: %s\n", u.name, from, message)}
func main() { room := &ChatRoom{} alice := NewUser("Alice", room) bob := NewUser("Bob", room)
alice.Send("hi") // [Bob] Alice: hi bob.Send("hello") // [Alice] Bob: hello}use std::cell::RefCell;
// The ChatRoom owns the users and routes messages between them.struct ChatRoom { users: Vec<String>, log: RefCell<Vec<String>>,}
impl ChatRoom { fn new() -> Self { ChatRoom { users: Vec::new(), log: RefCell::new(Vec::new()) } }
fn register(&mut self, name: &str) { self.users.push(name.to_string()); }
fn broadcast(&self, sender: &str, message: &str) { for user in &self.users { if user != sender { self.log .borrow_mut() .push(format!("[{user}] {sender}: {message}")); } } }}
fn main() { let mut room = ChatRoom::new(); room.register("Alice"); room.register("Bob");
room.broadcast("Alice", "hi"); room.broadcast("Bob", "hello");
for line in room.log.borrow().iter() { println!("{line}"); // [Bob] Alice: hi / [Alice] Bob: hello }}ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”- ข้อดี: เปลี่ยนสายใย reference แบบ many-to-many ที่พันกันยุ่งให้กลายเป็นการเชื่อมต่อแบบฮับและซี่ล้อที่เรียบง่าย
- ข้อดี: เพื่อนร่วมงานนำกลับมาใช้ซ้ำได้เพราะพึ่งพาแค่ interface ของ mediator ไม่ใช่พึ่งพากันเอง
- ข้อดี: ตรรกะการโต้ตอบอยู่ในที่เดียวที่อ่านได้ แทนที่จะกระจัดกระจายอยู่ทั่วเพื่อนร่วมงาน
- ข้อเสีย: mediator อาจพองตัวกลายเป็น god object ที่รู้ทุกอย่าง กลายเป็นความซับซ้อนตัวนั้นเองที่คุณพยายามกำจัด
- ข้อเสีย: เพิ่มชั้น indirection เข้ามาอีกหนึ่งชั้น ทำให้ตามยากว่าสุดท้ายใครเป็นคนตอบสนอง event
pattern ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “pattern ที่เกี่ยวข้อง”- Observer ก็แยกผู้ส่งออกจากผู้รับเหมือนกัน แต่กระจาย event ไปยัง subscriber ตรง ๆ ส่วน Mediator รวมศูนย์ตรรกะการกำหนดเส้นทางไว้ที่เดียว
- Facade ก็เสนอจุดติดต่อจุดเดียวเช่นกัน แต่ facade เพียงทำให้การเรียกทางเดียวเข้าสู่ซับซิสเต็มเรียบง่ายขึ้น ขณะที่ mediator ประสานงานการจราจรสองทางระหว่างเพื่อนที่เท่าเทียมกัน
| Mediator | Observer | Facade | |
|---|---|---|---|
| จุดประสงค์ | ประสาน object หลายตัวให้สื่อสารผ่านศูนย์กลาง | แจ้ง subscriber เมื่อ subject เปลี่ยน | ให้ interface ง่ายต่อ subsystem |
| ทิศทาง | many ↔ 1 (bidirectional) | 1 → many (push) | client → facade → subsystem |
| peer รู้จักกัน | ไม่รู้จักกัน (รู้จักแค่ mediator) | ไม่รู้จัก observer | ไม่สำคัญ |
| ตัวอย่าง | Redux store, event bus, chat server | RxJS, DOM events, pub/sub | axios, AWS SDK |