ข้ามไปยังเนื้อหา

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 เท่านั้น แล้ว mediator เป็นคนกำหนดเส้นทางข้อความให้
  • 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: hi
bob.send('hello'); // [Alice] Bob: hello
  • ข้อดี: เปลี่ยนสายใย reference แบบ many-to-many ที่พันกันยุ่งให้กลายเป็นการเชื่อมต่อแบบฮับและซี่ล้อที่เรียบง่าย
  • ข้อดี: เพื่อนร่วมงานนำกลับมาใช้ซ้ำได้เพราะพึ่งพาแค่ interface ของ mediator ไม่ใช่พึ่งพากันเอง
  • ข้อดี: ตรรกะการโต้ตอบอยู่ในที่เดียวที่อ่านได้ แทนที่จะกระจัดกระจายอยู่ทั่วเพื่อนร่วมงาน
  • ข้อเสีย: mediator อาจพองตัวกลายเป็น god object ที่รู้ทุกอย่าง กลายเป็นความซับซ้อนตัวนั้นเองที่คุณพยายามกำจัด
  • ข้อเสีย: เพิ่มชั้น indirection เข้ามาอีกหนึ่งชั้น ทำให้ตามยากว่าสุดท้ายใครเป็นคนตอบสนอง event
  • Observer ก็แยกผู้ส่งออกจากผู้รับเหมือนกัน แต่กระจาย event ไปยัง subscriber ตรง ๆ ส่วน Mediator รวมศูนย์ตรรกะการกำหนดเส้นทางไว้ที่เดียว
  • Facade ก็เสนอจุดติดต่อจุดเดียวเช่นกัน แต่ facade เพียงทำให้การเรียกทางเดียวเข้าสู่ซับซิสเต็มเรียบง่ายขึ้น ขณะที่ mediator ประสานงานการจราจรสองทางระหว่างเพื่อนที่เท่าเทียมกัน
MediatorObserverFacade
จุดประสงค์ประสาน object หลายตัวให้สื่อสารผ่านศูนย์กลางแจ้ง subscriber เมื่อ subject เปลี่ยนให้ interface ง่ายต่อ subsystem
ทิศทางmany ↔ 1 (bidirectional)1 → many (push)client → facade → subsystem
peer รู้จักกันไม่รู้จักกัน (รู้จักแค่ mediator)ไม่รู้จัก observerไม่สำคัญ
ตัวอย่างRedux store, event bus, chat serverRxJS, DOM events, pub/subaxios, AWS SDK
Mediator รวมศูนย์อะไร?
เพื่อนร่วมงานอ้างถึงกันอย่างไรภายใต้ Mediator pattern?
อะไรคือความเสี่ยงหลักของ Mediator pattern?