cv.cm

Lập trình · Mẫu thiết kế

Mẫu Mediator (Design Pattern) giải thích kèm ví dụ

6 phút đọc

Trả lời nhanh

Mediator là một mẫu thiết kế hành vi (behavioral), giảm phụ thuộc trực tiếp giữa các đối tượng bằng cách cho chúng giao tiếp qua một đối tượng mediator trung tâm. Thay vì mỗi thành phần gọi các thành phần khác, một thành phần báo cho mediator biết có chuyện xảy ra, và mediator quyết định cập nhật các thành phần nào. Điều này biến mạng lưới tham chiếu nhiều-nhiều rối rắm thành các liên kết một-nhiều đơn giản.

Mục đích và vấn đề mà mẫu này giải quyết

Mục đích: định nghĩa một đối tượng đóng gói cách một tập đối tượng tương tác với nhau. Mediator thúc đẩy liên kết lỏng bằng cách ngăn các đối tượng tham chiếu nhau một cách tường minh, và cho phép bạn thay đổi cách tương tác của chúng một cách độc lập. Đây là một trong các mẫu hành vi trong sách Gang of Four (GoF).

Hãy xét một biểu mẫu đăng ký có ô chọn “tài khoản công ty”, ô tên công ty, ô email và nút gửi. Khi tick vào ô chọn, ô công ty phải hiện ra và trở thành bắt buộc. Nút gửi chỉ được bật khi mọi ô bắt buộc đều hợp lệ. Nếu mỗi thành phần cập nhật trực tiếp các thành phần khác, ô chọn phải biết ô công ty và nút gửi, ô email phải biết nút gửi, và cứ thế. Mỗi quy tắc mới lại thêm tham chiếu chéo, và không thành phần nào dùng lại được trong biểu mẫu khác.

Với mediator, các thành phần chỉ báo các sự kiện như “tôi đã thay đổi”. Đối tượng biểu mẫu, đóng vai trò mediator, giữ mọi quy tắc về cách các ô ảnh hưởng lẫn nhau. Các thành phần trở nên đơn giản và dùng lại được, còn logic tương tác nằm ở một chỗ.

Cấu trúc và các thành phần

Vai tròTrách nhiệm
MediatorInterface có phương thức mà các colleague gọi để báo sự kiện, như notify(sender, event)
Concrete mediatorBiết mọi colleague và cài đặt các quy tắc phối hợp giữa chúng
ColleagueMột thành phần giữ tham chiếu đến mediator và báo sự kiện của mình thay vì gọi các colleague khác

Nếu không có mediator, n thành phần đều nói chuyện với nhau có thể cần tới n × (n − 1) / 2 liên kết. Có mediator, mỗi thành phần chỉ có đúng một liên kết: tới mediator.

Cài đặt bằng TypeScript

Mỗi ô báo thay đổi cho mediator. SignUpForm là concrete mediator: nó hiện hoặc ẩn ô công ty và bật nút gửi.

interface Mediator {
  notify(sender: Component, event: string): void;
}

class Component {
  constructor(protected mediator: Mediator, readonly name: string) {}
}

class Checkbox extends Component {
  checked = false;
  toggle() { this.checked = !this.checked; this.mediator.notify(this, "change"); }
}
class TextField extends Component {
  value = "";
  visible = true;
  type(text: string) { this.value = text; this.mediator.notify(this, "change"); }
}
class Button extends Component { enabled = false; }

class SignUpForm implements Mediator {
  isCompany = new Checkbox(this, "isCompany");
  company = new TextField(this, "company");
  email = new TextField(this, "email");
  submit = new Button(this, "submit");
  constructor() { this.company.visible = false; }

  notify(sender: Component, event: string) {
    if (sender === this.isCompany) this.company.visible = this.isCompany.checked;
    const emailOk = this.email.value.includes("@");
    const companyOk = !this.company.visible || this.company.value.length > 0;
    this.submit.enabled = emailOk && companyOk;
    console.log(sender.name, event, "-> submit enabled:", this.submit.enabled);
  }
}

const form = new SignUpForm();
form.email.type("[email protected]"); // email change -> submit enabled: true
form.isCompany.toggle();            // isCompany change -> submit enabled: false
form.company.type("Acme");          // company change -> submit enabled: true

Lưu ý Checkbox và TextField không hề biết các ô khác tồn tại. Bạn có thể dùng lại chúng trong một biểu mẫu hoàn toàn khác bằng cách gán cho chúng một mediator khác.

Khi nào nên dùng và khi nào không

  • Dùng khi một nhóm đối tượng giao tiếp theo cách phức tạp nhưng xác định rõ, và các phụ thuộc giữa chúng khó theo dõi.
  • Dùng khi bạn muốn dùng lại các thành phần hiện không thể tách rời vì chúng tham chiếu lẫn nhau.
  • Dùng khi các quy tắc tương tác thay đổi thường xuyên và bạn muốn sửa chúng ở một chỗ.
  • Đừng dùng cho hai đối tượng chỉ đơn giản gọi nhau; một lời gọi trực tiếp rõ ràng hơn.
  • Đừng dùng khi các thành phần chỉ cần phát sự kiện cho bất kỳ ai quan tâm. Một event emitter thông thường (Observer) nhẹ hơn.

Ứng dụng thực tế

  • Bộ điều khiển biểu mẫu và hộp thoại trong các framework GUI: component cha sở hữu state và quyết định cách các ô nhập con ảnh hưởng lẫn nhau, còn các con chỉ phát sự kiện. Trong React, việc “nâng state lên” component cha chung theo đúng ý tưởng này.
  • Ứng dụng chat: người dùng không gửi tin trực tiếp cho nhau; phòng chat hoặc máy chủ nhận từng tin và chuyển cho đúng thành viên.
  • Message broker và event bus giữa các dịch vụ: bên gửi publish lên broker, broker định tuyến thông điệp đến các bên nhận mà bên gửi không biết.
  • Kiểm soát không lưu là phép so sánh kinh điển: máy bay không thương lượng với mọi máy bay khác, chúng chỉ nói chuyện với tháp kiểm soát.

Mediator so với Observer và các mẫu khác

MẫuKhác với Mediator ở điểm nào
ObserverObserver là phát một chiều: subject thông báo cho bao nhiêu listener tùy ý và không biết chúng làm gì. Mediator biết các colleague của mình và điều phối chúng theo cả hai chiều. Mediator thường được cài đặt bằng Observer.
FacadeFacade cho client một interface đơn giản hơn tới hệ thống con, và hệ thống con không biết facade tồn tại. Các colleague biết mediator của mình và nói chuyện với nó.
Chain of ResponsibilityChuỗi chuyển yêu cầu dọc một hàng handler; mediator là một trung tâm duy nhất gửi mỗi sự kiện đến đúng các thành phần.
CommandCác colleague có thể gửi đối tượng command cho mediator, rồi mediator quyết định ai thực hiện chúng.

Câu hỏi thường gặp

Mẫu Mediator khác mẫu Observer ở điểm nào?

Với Observer, subject phát sự kiện cho các subscriber mà nó không biết, và giao tiếp là một chiều. Với Mediator, một đối tượng trung tâm biết các thành phần tham gia và điều phối cách chúng ảnh hưởng lẫn nhau.

Event bus có phải là mediator không?

Event bus chỉ chuyển tiếp sự kiện cho subscriber thì gần với Observer, hay publish-subscribe, hơn. Nó trở thành mediator khi chứa các quy tắc quyết định cách các thành phần phản ứng với nhau.

Mediator có làm mã khó theo dõi hơn không?

Có thể. Lời gọi trực tiếp dễ lần theo, còn lời gọi qua trung gian phải đi qua một đối tượng trung tâm. Mẫu này đáng giá khi phương án thay thế là nhiều thành phần tham chiếu lẫn nhau.

Một thành phần có thể có nhiều hơn một mediator không?

Thường một thành phần có một mediator, được gán khi nó được tạo. Một thành phần có thể được dùng lại với các mediator khác nhau trong các ngữ cảnh khác nhau, đó là một trong những lợi ích chính của mẫu này.

Tất cả hướng dẫn lập trình