中介者模式
发表于更新于
设计模式中介者模式
Rif一、一句话概括
用一个中介对象来封装一系列对象之间的复杂交互,使得这些对象不再需要显式地相互引用,从而降低它们之间的耦合度。它将网状的通信结构转变为星形的通信结构。
二、为什么需要它 (Why - The Pain Point)
想象一个复杂的 GUI 对话框,上面有多个控件:一个文本框(TextBox)、一个按钮(Button)、一个列表框(ListBox)、一个复选框(Checkbox)。它们之间存在复杂的交互逻辑:
- 当文本框内容为空时,按钮应被禁用。
- 当在列表框中选择一项时,该项的内容应显示在文本框中。
- 当复选框被选中时,列表框中的所有项都应被清除。
- … 等等
没有中介者模式的糟糕设计:
每个控件都需要直接持有其他相关控件的引用,并直接调用它们的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class TextBox { private Button button; private ListBox listBox; public void onTextChanged() { if (this.getText().isEmpty()) { button.setEnabled(false); } else { button.setEnabled(true); } } }
class ListBox { private TextBox textBox; private Checkbox checkbox;
public void onSelectionChanged() { textBox.setText(this.getSelected()); } }
|
痛点:蜘蛛网式的耦合 (Spiderweb Coupling)
- 高度耦合:每个组件都与其他多个组件紧密耦合,形成一个难以理解和维护的“蜘蛛网”。
- 难以复用:
TextBox 组件因为它依赖于特定的 Button 和 ListBox,所以很难将它单独拿到另一个对话框中复用。
- 违反开闭原则:如果增加一个新的控件,比如一个滑动条
Slider,并且它需要与 TextBox 和 Button 交互,那么你可能需要修改 TextBox 和 Button 的代码来添加对 Slider 的引用和处理逻辑。
核心问题:如何管理一组对象之间复杂的、多对多的交互关系,从而避免它们之间产生紧密的网状耦合?
三、它是什么 (What - The Solution)
中介者模式通过引入一个中心化的中介者(Mediator) 对象来解决这个问题。所有的“同事”对象(Colleague)不再直接通信,而是将消息发送给中介者,由中介者来协调和转发。
核心思想和角色:
- **中介者接口 (Mediator)**:
- 定义了同事对象与中介者对象之间通信的接口。通常包含一个方法,如
notify(Component sender, String event),用于接收同事对象发来的通知。
- **具体中介者 (Concrete Mediator)**:
- 实现了中介者接口。
- 它了解并持有所有同事对象的引用。
- 它封装了对象之间复杂的交互逻辑。当它从一个同事对象接收到消息后,它会根据预设的逻辑去调用其他一个或多个同事对象的方法。
- **同事接口/抽象类 (Colleague)**:
- 定义了同事对象的通用接口。
- 它持有一个中介者对象的引用。
- **具体同事 (Concrete Colleague)**:
- 实现了同事接口。
- 当自身状态发生改变,需要通知其他同事时,它不会直接调用其他同事,而是调用中介者的
notify() 方法,将自己作为发送者传进去。
- 它也提供了供中介者调用的方法,以改变自身状态。
工作流程:
当 ListBox 中有选项被选中时:
ListBox 调用 mediator.notify(this, "selectionChanged")。
ConcreteMediator 接收到通知,知道是 ListBox 发来的 selectionChanged 事件。
- 根据内部逻辑,
ConcreteMediator 调用 textBox.setText(...)。
现在,ListBox 只认识 Mediator,完全不知道 TextBox 的存在。
四、怎么做 (How - The Blueprint)
基本步骤(以 GUI 对话框为例):
- 创建同事类(
Button, TextBox 等),每个同事类都持有一个 Mediator 的引用。
- **创建中介者接口
Mediator**,定义一个 notify 方法。
- **创建具体中介者
DialogMediator**,实现 Mediator 接口。
- 它持有所有同事对象(
Button, TextBox 等)的引用。
- 在
notify 方法中实现复杂的协调逻辑。
- 在客户端代码中,创建所有同事对象和中介者对象,并将中介者注入到每个同事中,同时将同事注册到中介者中。
Java 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| interface Mediator { void notify(Component sender, String event); }
abstract class Component { protected Mediator mediator;
public Component(Mediator mediator) { this.mediator = mediator; }
public void click() { mediator.notify(this, "click"); }
public void keypress() { mediator.notify(this, "keypress"); } }
class Button extends Component { private boolean enabled; public Button(Mediator mediator) { super(mediator); } public void setEnabled(boolean enabled) { this.enabled = enabled; System.out.println("Button is now " + (enabled ? "enabled" : "disabled")); } }
class TextBox extends Component { private String content = ""; public TextBox(Mediator mediator) { super(mediator); } public void setContent(String content) { this.content = content; } public String getContent() { return content; } }
class ListBox extends Component { private String selection = ""; public ListBox(Mediator mediator) { super(mediator); } public void setSelection(String selection) { this.selection = selection; System.out.println("ListBox selected: " + selection); this.click(); } }
class AuthenticationDialog implements Mediator { private TextBox usernameBox; private TextBox passwordBox; private Button loginButton; private ListBox userType;
public void setUsernameBox(TextBox usernameBox) { this.usernameBox = usernameBox; } public void setPasswordBox(TextBox passwordBox) { this.passwordBox = passwordBox; } public void setLoginButton(Button loginButton) { this.loginButton = loginButton; } public void setUserType(ListBox userType) { this.userType = userType; }
@Override public void notify(Component sender, String event) { System.out.println("Mediator received notification from " + sender.getClass().getSimpleName() + " with event " + event); if (sender == usernameBox || sender == passwordBox) { if (!usernameBox.getContent().isEmpty() && !passwordBox.getContent().isEmpty()) { loginButton.setEnabled(true); } else { loginButton.setEnabled(false); } } else if (sender == userType && "click".equals(event)) { if ("Admin".equals(userType.selection)) { System.out.println("Admin selected, showing special fields (logic in mediator)..."); } } } }
public class MediatorPatternDemo { public static void main(String[] args) { AuthenticationDialog mediator = new AuthenticationDialog();
TextBox username = new TextBox(mediator); TextBox password = new TextBox(mediator); Button login = new Button(mediator); ListBox type = new ListBox(mediator);
mediator.setUsernameBox(username); mediator.setPasswordBox(password); mediator.setLoginButton(login); mediator.setUserType(type);
System.out.println("--- Initial State ---"); username.keypress();
System.out.println("\n--- User fills in details ---"); username.setContent("admin"); password.setContent("12345"); username.keypress();
System.out.println("\n--- User selects a type ---"); type.setSelection("Admin"); } }
|
五、UML 类图
下面是上述例子的 PlantUML 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @startuml skinparam classAttributeIconSize 0 hide empty members
interface Mediator { + void notify(Component sender, String event) }
abstract class Component { # Mediator mediator + Component(Mediator m) }
class Button extends Component class TextBox extends Component class ListBox extends Component
class AuthenticationDialog implements Mediator { - TextBox usernameBox - Button loginButton ... + void notify(Component sender, String event) }
' Relationships ' Colleagues HAVE-A Mediator Component o-- "1" Mediator
' Mediator HAS-A list of Colleagues AuthenticationDialog *-- "1..*" Component
' Client uses both class MediatorPatternDemo {} MediatorPatternDemo ..> AuthenticationDialog MediatorPatternDemo ..> Component @enduml
|
图示解读:
- 所有
Component(同事)都与 Mediator(中介者)关联。
AuthenticationDialog(具体中介者)反过来也与所有 Component 关联。
- 形成了一个清晰的星形结构,
Mediator 位于中心。
六、优缺点分析
优点 (Pros):
- 降低耦合度:将网状的多对多关系转变为星形的点对多关系,同事类之间不再直接依赖,只依赖于中介者,极大地降低了系统的耦合性。
- 提高可复用性:同事类因为不再依赖其他同事,变得更容易被复用。
- 集中控制逻辑:复杂的交互逻辑被封装在中介者中,使得逻辑更集中,易于理解和维护。
- 符合开闭原则:增加新的同事类通常不需要修改其他同事类,只需要修改中介者即可。
缺点 (Cons):
- 中介者可能变得过于复杂:将所有交互逻辑都集中到一个中介者中,可能导致中介者类自身变得非常庞大和复杂,成为一个难以维护的“上帝对象”(God Object)。
七、在 Java 中的应用
java.util.Timer: Timer 类可以被看作一个中介者。你可以安排多个 TimerTask(同事)在指定时间执行。Timer 负责管理所有任务的调度,而各个 TimerTask 之间互不关心。
- GUI 框架: 各种 GUI 框架的事件处理机制,如 Java Swing 中的
ActionListener,虽然不完全是经典的中介者模式,但思想相通。通常会有一个控制器(Controller)或表现器(Presenter)对象充当中介者,来协调视图(View)中各个组件的交互。
- Executor 框架:
java.util.concurrent.Executor 接口及其实现(如 ThreadPoolExecutor)也扮演了中介者的角色。你提交 Runnable 或 Callable 任务(同事)给它,它负责协调线程池中的线程(另一组同事)来执行这些任务。任务与线程之间完全解耦。