Mediator 模式
为什么使用此类
请大家想象一下一个乱糟糟的开发小组的工作状态。小组中的 10 个成员虽然一起协同工作,但是意见难以统一,总是互相指挥,导致工作进度始终滞后。不仅如此,他们还十分在意编码细节,经常为此争执不下。
这时,我们就需要一个中立的仲裁者站出来说:“各位,请大家将情况报告给我,我来负责仲裁。我会从团队整体出发进行考虑,然后下达指示,但我不会评价大家的工作细节。”这样,当出现争执时大家就会找仲裁者进行商量,仲裁者会负责统一大家的意见。
最后,整个团队的交流过程就变为了组员向仲裁者报告,仲裁者向组员下达指示。组员之间不再相互询问和相互指示。
示例代码
/**
* EN: Real World Example for the Mediator design pattern
*
* Need: To have a messaging application to notify groups of people. Users
* should not know about each other.
*
* Solution: Create a mediator to manage subscriptions and messages
*/
/**
* EN: Extending the Mediator interface to have a payload to include messages
*/
interface Mediator {
notify(sender: object, event: string, payload?: string): void;
}
/**
* EN: The user plays the role of the independent component. It has an
* instance of the mediator.
*/
class User {
constructor(public name: string, private mediator: Mediator) {
this.mediator.notify(this, 'subscribe');
}
receiveMessage(message: string) {
console.log(`Message received by ${this.name}: ${message}`);
}
publishMessage(message: string) {
this.mediator.notify(this, 'publish', message);
}
}
/**
* EN: The app is the concrete Mediator and implements all the events that
* collaborators can notify: subscribe and publish
*/
class ChatAppMediator implements Mediator {
private users: User[] = [];
public notify(sender: object, event: string, payload?: string): void {
if (event === 'subscribe') {
const user = sender as User;
console.log(`Subscribing ${user.name}`);
this.users.push(user);
}
if (event === 'publish') {
console.log(`Publishing message "${payload}" to the group`);
const usersExcludingSender = this.users.filter(u => u !== sender);
for (const user of usersExcludingSender) {
user.receiveMessage(payload);
}
}
}
}
/**
* EN: The client code. Creating a user automatically subscribes them to the
* group.
*/
const chatAppMediator = new ChatAppMediator();
const user1 = new User('Lightning', chatAppMediator);
const user2 = new User('Doc', chatAppMediator);
const user3 = new User('Mater', chatAppMediator);
user1.publishMessage('Catchaw');
user2.publishMessage('Ey kid');
user3.publishMessage('Tomato');
运行结果
PS design_patern> ts-node "d:\code\design_patern\src\mediator\main.ts"
Subscribing Lightning
Subscribing Doc
Subscribing Mater
Publishing message "Catchaw" to the group
Message received by Doc: Catchaw
Message received by Mater: Catchaw
Publishing message "Ey kid" to the group
Message received by Lightning: Ey kid
Message received by Mater: Ey kid
Publishing message "Tomato" to the group
Message received by Lightning: Tomato
Message received by Doc: Tomato
拓展思路的要点
当发生分散灾难时
示例程序中的 ChatAppMediator
类的 notify
方法稍微有些复杂。如果发生需求变更,该方法中很容易发生 Bug。不过这并不是什么问题。因为即使 notify
方法中发生了 Bug,由于其他地方并没有控制消息发布和订阅的逻辑处理,因此只要调试该方法就能很容易地找出 Bug 的原因。请试想一下,如果这段逻辑分散在 User
类中,那么无论是编写代码还是调试代码和修改代码,都会非常困难。通常情况下,面向对象编程可以帮助我们分散处理,避免处理过于集中,也就是说可以“分而治之”。但是在本章中的示例程序中,把处理分散在各个类中是不明智的。如果只是将应当分散的处理分散在各个类中,但是没有将应当集中的处理集中起来,那么这些分散的类最终只会导致灾难。
在这个示例程序中,ChatAppMediator
类作为中介者(Mediator),负责管理和协调各个用户(User)的交互。notify
方法集中处理了所有消息的发布和订阅逻辑,这样可以确保逻辑的一致性和可维护性。如果将这些逻辑分散到各个用户类中,不仅会增加代码的复杂性,还会使得调试和维护变得更加困难。因此,将这些逻辑集中在中介者类中是更明智的选择。
通信线路的增加
假设现在有 A 和 B 这 2 个实例,它们之间互相通信(相互调用方法),那么通信线路有两条,即 A-B 和 A-B。如果是有 A、B 和 C 这 3 个实例,那么就会有 6 条通信线路,即 A-B、A-C、B-C、B-A 和 C-A。如果有 4 个实例,会有 12 条通信线路;5 个实例就会有 20 条通信线路,而 6 个实例则会有 30 条通信线路。如果存在很多这样的互相通信的实例,那么程序结构会变得非常复杂。可能会有读者认为,如果实例很少就不需要 Mediator 模式了。但是需要考虑到的是,即使最初实例很少,很可能随着需求变更实例数量会慢慢变多,迟早会暴露出问题。
哪些角色可以复用
ConcreteColleague 角色可以复用,但 ConcreteMediator 角色很难复用。例如,假设我们现在需要制作另外一个对话框。这时,我们可将扮演 ConcreteColleague 角色的 colleagueButton 类、 colleagueTextField 类和 colleagueCheckbox 类用于新的对话框中。这是因为在 ConcreteColleague 角色中并没有任何依赖于特定对话框的代码。在示例程序中,依赖于特定应用程序的部分都被封装在扮演 ConcreteMediator 角色的 LoginFrame 类中,依赖于特定应用程序就意味着难以复用。因此, LoginFrame 类很难在其他对话框中被复用。
相关的设计模式
- Facade 模式
- Observer 模式