GoF设计模式——中介者模式
为什么需要中介者模式假设在设计一个智能家居系统家里有空调、窗帘、灯三种设备。业务规则是空调开启时自动关闭窗帘避免冷气流失窗帘关闭时自动开灯补充采光。最直觉的写法是让设备之间直接互相调用class AirConditioner {private Curtain curtain;private Light light;public void turnOn() { // 空调开启逻辑 curtain.close(); // 直接调用窗帘 }}class Curtain {private Light light;public void close() { // 窗帘关闭逻辑 light.turnOn(); // 直接调用灯 }}这种写法的问题很快暴露空调需要知道窗帘窗帘需要知道灯设备之间形成复杂的网状依赖。如果新增一个设备如加湿器需要修改所有相关设备的代码。更麻烦的是如果业务规则变了比如空调开启时也要开灯就得改空调的代码违反开闭原则。中介者模式解决的就是这种多个对象之间存在复杂联动关系的耦合问题。把所有联动规则集中到一个中介者对象里设备只跟中介者通信互不认识。概念中介者模式Mediator Pattern是一种行为型设计模式核心思想是用一个中介对象封装一系列对象之间的交互使各对象不需要显式地互相引用从而使其耦合松散而且可以独立改变它们之间的交互。可以把它想象成航空管制塔台每架飞机同事对象不需要知道其他飞机的位置和航线只需要向塔台中介者报告自己的状态塔台根据全局信息协调所有飞机的起降和飞行路径。飞机之间互不通信全部通过塔台协调。中介者模式涉及四个角色Mediator抽象中介者定义中介者接口用于各同事对象之间的通信ConcreteMediator具体中介者实现中介者接口负责协调各同事对象的交互关系需要知道所有同事类Colleague抽象同事类定义同事类接口维护一个对中介者对象的引用ConcreteColleague具体同事类实现同事类接口每个同事类只知道自己行为通过中介者与其他同事交互实现继承继承持有同事列表持有中介者引用«interface»Mediatorregister(colleague: Colleague)send(message: String, colleague: Colleague)ConcreteMediator-colleagues: Listregister(colleague: Colleague)send(message: String, colleague: Colleague)«abstract»Colleague#mediator: Mediatorsend(message: String)receive(message: String)ConcreteColleagueAsend(message: String)receive(message: String)ConcreteColleagueBsend(message: String)receive(message: String)图中各类之间的关系Mediator 接口定义了 register 和 send 方法ConcreteMediator 实现该接口并持有 ListColleague 抽象类持有 Mediator 引用并通过它发送消息——同事类之间不直接通信全部通过中介者协调。实现GoF 的标准实现中中介者维护所有同事的引用同事通过中介者转发消息。当同事状态变化时通知中介者中介者根据业务规则决定通知哪些其他同事。标准实现定义一个 Mediator 接口作为抽象中介者声明 register注册同事和 send转发消息方法。Colleague 作为抽象同事类持有中介者引用提供 send发送消息和 receive接收消息方法。具体中介者 ConcreteMediator 维护同事列表在 send 方法中实现消息转发策略。具体同事类 ConcreteColleagueA 和 ConcreteColleagueB 通过中介者与其他同事交互。// 抽象中介者interface Mediator {public void register(Colleague colleague);public void send(String message, Colleague colleague);}// 抽象同事类abstract class Colleague {protected Mediator mediator;public Colleague(Mediator mediator) { this.mediator mediator; } public abstract void receive(String message); public abstract void send(String message);}// 具体中介者class ConcreteMediator implements Mediator {private List colleagues new ArrayList();public void register(Colleague colleague) { colleagues.add(colleague); } public void send(String message, Colleague colleague) { // 转发给除发送者外的所有同事 for (Colleague c : colleagues) { if (c ! colleague) { c.receive(message); } } }}// 具体同事类Aclass ConcreteColleagueA extends Colleague {public ConcreteColleagueA(Mediator mediator) {super(mediator);}public void receive(String message) { System.out.println(ColleagueA 收到消息: message); } public void send(String message) { System.out.println(ColleagueA 发送消息: message); mediator.send(message, this); }}// 具体同事类Bclass ConcreteColleagueB extends Colleague {public ConcreteColleagueB(Mediator mediator) {super(mediator);}public void receive(String message) { System.out.println(ColleagueB 收到消息: message); } public void send(String message) { System.out.println(ColleagueB 发送消息: message); mediator.send(message, this); }}角色对照Mediator抽象中介者Mediator 接口定义 register 和 send 方法ConcreteMediator具体中介者ConcreteMediator维护同事列表实现消息转发策略Colleague抽象同事类Colleague 抽象类持有中介者引用提供 send 和 receive 方法ConcreteColleague具体同事类ConcreteColleagueA、ConcreteColleagueB通过中介者与其他同事交互关键点同事类只负责自身状态通过中介者转发消息中介者维护所有同事引用根据业务规则决定消息转发策略。同事之间不直接通信全部通过中介者协调。引入一个具体场景智能家居系统空调、窗帘、灯三种设备通过中控中介者协调联动。空调开启时自动关闭窗帘避免冷气流失窗帘关闭时自动开灯补充采光。// 抽象中介者interface SmartHomeMediator {public void register(Device device);public void notify(String event, Device device);}// 抽象同事类设备基类abstract class Device {protected SmartHomeMediator mediator;protected String name;protected boolean state;public Device(SmartHomeMediator mediator, String name) { this.mediator mediator; this.name name; this.state false; } public String getName() { return name; } public boolean getState() { return state; } public void setState(boolean state) { this.state state; System.out.println(name (state ? 开启 : 关闭)); mediator.notify(state ? on : off, this); } public abstract void handleEvent(String event);}// 具体中介者智能家居中控class ConcreteSmartHomeMediator implements SmartHomeMediator {private List devices new ArrayList();public void register(Device device) { devices.add(device); } public void notify(String event, Device device) { // 空调开启 → 关闭窗帘 if (device instanceof AirConditioner event.equals(on)) { for (Device d : devices) { if (d instanceof Curtain) { d.setState(false); } } } // 窗帘关闭 → 开启灯 if (device instanceof Curtain event.equals(off)) { for (Device d : devices) { if (d instanceof Light) { d.setState(true); } } } }}// 具体同事类空调class AirConditioner extends Device {public AirConditioner(SmartHomeMediator mediator) {super(mediator, “空调”);}public void handleEvent(String event) { // 空调不处理其他设备的事件 }}// 具体同事类窗帘class Curtain extends Device {public Curtain(SmartHomeMediator mediator) {super(mediator, “窗帘”);}public void handleEvent(String event) { // 窗帘不处理其他设备的事件 }}// 具体同事类灯class Light extends Device {public Light(SmartHomeMediator mediator) {super(mediator, “灯”);}public void handleEvent(String event) { // 灯不处理其他设备的事件 }}角色对照Mediator抽象中介者SmartHomeMediator 接口定义 register 和 notify 方法ConcreteMediator具体中介者ConcreteSmartHomeMediator维护设备列表实现联动规则Colleague抽象同事类Device 抽象类持有中介者引用状态变化时通知中介者ConcreteColleague具体同事类AirConditioner、Curtain、Light具体设备实现关键点设备状态变化时调用 setState在 setState 中通知中介者中介者根据事件类型和设备类型执行联动规则如果业务规则变化比如空调开启时也要开灯只需修改中介者的 notify 方法设备类不变。总结本质用一个中介对象封装多个对象之间的交互将网状依赖转化为星形结构。什么时候用多个对象之间存在复杂的网状依赖关系需要集中管理对象间的交互规则想要降低对象间的耦合度提高可维护性什么时候不用对象之间只有简单的两两交互引入中介者反而增加复杂度性能要求极高中介者转发消息的开销不可接受中介者本身会变得过于庞大复杂上帝对象简单记忆中介者管协调同事只管报规则集中改耦合自然少。相似模式区分总览模式 核心意图 典型场景中介者 协调多个对象间的复杂交互 智能家居中控、聊天室、GUI组件联动观察者 一对多的通知机制 事件监听、消息订阅、数据绑定门面 为子系统提供统一入口 API封装、SDK入口、简化调用中介者 vs 观察者维度 中介者模式 观察者模式核心意图 集中协调多对象间的双向交互 一对多的单向通知结构差异 中介者知道所有同事同事只知道中介者 主题知道所有观察者观察者只知道主题关注点 对象间的交互规则和联动逻辑 状态变化的通知分发典型场景 智能家居中控、聊天室、工作流引擎 事件监听、消息订阅、数据绑定逐步区分法如果对象间需要双向交互A变化影响BB变化可能再影响A→ 选中介者如果只是单向通知主题变化通知观察者观察者不反向影响主题→ 选观察者如果联动规则复杂需要集中管理 → 选中介者简单记忆口诀中介者管双向协调观察者管单向通知。推荐大多数场景下复杂联动用中介者简单通知用观察者。Spring事件机制是观察者的变体但可以用于简单联动场景。中介者 vs 门面维度 中介者模式 门面模式核心意图 协调多对象间的交互降低耦合 为复杂子系统提供统一入口简化调用结构差异 中介者参与业务逻辑协调对象间的交互 门面只是封装调用入口不参与子系统内部逻辑关注点 对象间的交互规则 子系统的使用接口典型场景 聊天室、GUI组件联动、工作流引擎 API封装、SDK入口、简化配置逐步区分法如果需要协调多个对象间的交互对象间有联动→ 选中介者如果只是简化调用入口提供一个统一接口调用多个子系统→ 选门面如果对象需要反向通知中介者 → 选中介者门面是单向的简单记忆口诀中介者管交互门面管入口。推荐门面用于简化外部调用中介者用于协调内部交互。两者可以组合使用门面作为外部入口中介者协调内部逻辑。练习题目智能家居中控系统题目描述小明正在设计一个智能家居系统系统中有灯Light、窗帘Curtain、空调AC三种设备它们通过智能家居中控中介者来协调联动。设备之间不直接通信所有联动逻辑由中控负责。中控的联动规则空调开启 → 自动关闭窗帘避免冷气流失空调关闭 → 自动打开窗帘窗帘关闭 → 自动开灯补充采光窗帘打开 → 自动关灯自然光足够注意联动可能产生连锁反应。例如开启空调 → 关闭窗帘 → 开灯。同一设备在同一次连锁中状态只改变一次避免循环触发。输入描述第一行包含三个字符串分别是灯、窗帘、空调的名称用空格分隔。第二行包含三个字符串分别是灯、窗帘、空调的初始状态on 或 off用空格分隔。接下来每行包含一个操作格式为设备名 操作操作为 on 或 off直到输入结束。输出描述对于每个操作按联动触发顺序输出每个状态发生变化的设备格式为设备名 on 或设备名 off。若操作未导致状态变化设备已在目标状态则不输出。输入示例Light Curtain ACoff on offAC onAC offCurtain off输出示例AC onCurtain offLight onAC offCurtain onLight offCurtain offLight on解题思路本题的核心是中介者模式的连锁反应机制。角色对应Mediator抽象中介者Mediator 接口定义 send 方法ConcreteMediator具体中介者SmartHomeMediator实现联动规则空调→窗帘→灯Colleague抽象同事类Device 抽象类持有中介者引用提供 send 和 receive 方法ConcreteColleague具体同事类Light、Curtain、AC具体设备实现连锁反应机制用户操作设备如开启空调→ 调用 device.send(“on”)send 方法更新状态并输出然后通知中介者 mediator.send(“on”, this)中介者根据联动规则调用相关设备的 receive 方法如关闭窗帘receive 方法更新状态并输出再次通知中介者形成递归连锁避免循环触发在 send 和 receive 中检查 if (!status.equals(message))如果状态已经是目标状态则不处理import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String line sc.nextLine();String[] names line.split( “);line sc.nextLine();String[] status line.split(” );Mediator mediator new SmartHomeMediator(); Light light new Light(mediator, names[0], status[0]); Curtain curtain new Curtain(mediator, names[1], status[1]); AC ac new AC(mediator, names[2], status[2]); mediator.setLight(light); mediator.setCurtain(curtain); mediator.setAc(ac); MapString, Device map new HashMap(); map.put(names[0], light); map.put(names[1], curtain); map.put(names[2], ac); while (sc.hasNext()) { String name sc.next(); String s sc.next(); Device d map.get(name); d.send(s); } }}// 抽象中介者interface Mediator {public void send(String message, Device device);}// 具体中介者智能家居中控class SmartHomeMediator implements Mediator {private Light light;private Curtain curtain;private AC ac;public void setLight(Light light) { this.light light; } public void setCurtain(Curtain curtain) { this.curtain curtain; } public void setAc(AC ac) { this.ac ac; } public void send(String message, Device device) { // 空调状态变化触发窗帘联动 if (device instanceof AC) { if (message.equals(on)) { curtain.receive(off); // 规则1空调开 → 关窗帘 } else { curtain.receive(on); // 规则2空调关 → 开窗帘 } } // 窗帘状态变化触发灯联动 else if (device instanceof Curtain) { if (message.equals(on)) { light.receive(off); // 规则4窗帘开 → 关灯 } else { light.receive(on); // 规则3窗帘关 → 开灯 } } // 灯状态变化不触发联动 }}// 抽象同事类设备基类abstract class Device {protected Mediator mediator;protected String name;protected String status;public Device(Mediator mediator, String name, String status) { this.mediator mediator; this.name name; this.status status; } public String getName() { return name; } public String getStatus() { return status; } // 用户操作触发状态变化 public void send(String message) { if (!status.equals(message)) { System.out.println(name message); status message; mediator.send(message, this); // 通知中介者 } } // 中介者通知触发状态变化 public void receive(String message) { if (!status.equals(message)) { System.out.println(name message); status message; mediator.send(message, this); // 状态变化后通知中介者形成连锁 } }}// 具体同事类灯class Light extends Device {public Light(Mediator mediator, String name, String status) {super(mediator, name, status);}}// 具体同事类窗帘class Curtain extends Device {public Curtain(Mediator mediator, String name, String status) {super(mediator, name, status);}}// 具体同事类空调class AC extends Device {public AC(Mediator mediator, String name, String status) {super(mediator, name, status);}}扩展实际项目中的中介者模式Spring事件机制ApplicationEventPublisherSpring的事件机制本质上是中介者模式的变体ApplicationEventPublisher 作为中介者事件监听器作为同事类通过事件机制解耦组件间的交互。场景订单系统中订单创建后需要通知库存系统扣减库存、通知支付系统生成支付单、通知消息系统发送通知。如果订单直接依赖库存、支付、消息系统耦合度极高。角色对照MediatorApplicationEventPublisherSpring容器提供Colleague各个事件监听器EventListener 标注的方法事件对象同事之间传递的消息载体// 事件对象订单创建事件public class OrderCreatedEvent extends ApplicationEvent {private String orderId;private BigDecimal amount;public OrderCreatedEvent(Object source, String orderId, BigDecimal amount) { super(source); this.orderId orderId; this.amount amount; } public String getOrderId() { return orderId; } public BigDecimal getAmount() { return amount; }}// 同事类A库存服务监听器Servicepublic class InventoryEventListener {EventListenerpublic void handleOrderCreated(OrderCreatedEvent event) {System.out.println(“扣减库存订单号” event.getOrderId());// 调用库存服务扣减库存}}// 同事类B支付服务监听器Servicepublic class PaymentEventListener {EventListenerpublic void handleOrderCreated(OrderCreatedEvent event) {System.out.println(“生成支付单金额” event.getAmount());// 调用支付服务生成支付单}}// 同事类C消息服务监听器Servicepublic class MessageEventListener {EventListenerpublic void handleOrderCreated(OrderCreatedEvent event) {System.out.println(“发送订单创建通知订单号” event.getOrderId());// 调用消息服务发送通知}}// 订单服务发布事件不依赖任何监听器Servicepublic class OrderService {Autowiredprivate ApplicationEventPublisher publisher; // 注入中介者public void createOrder(String orderId, BigDecimal amount) { // 创建订单逻辑... System.out.println(订单创建成功 orderId); // 发布事件通知所有监听器不需要知道有哪些监听器 publisher.publishEvent(new OrderCreatedEvent(this, orderId, amount)); }}关键点ApplicationEventPublisher 作为中介者发布事件后自动通知所有监听器监听器通过 EventListener 注解声明自己关心的消息类型订单服务只需要发布事件不需要知道有哪些监听器耦合度降到最低。GUI对话框组件协调GUI界面中多个控件按钮、文本框、下拉框之间的联动逻辑复杂时使用中介者集中管理交互规则。场景用户注册对话框包含用户名输入框、密码输入框、确认密码框、注册按钮。联动规则用户名输入后检查是否已存在密码和确认密码不一致时提示错误所有验证通过后注册按钮才可用。角色对照MediatorDialogMediator 接口ConcreteMediatorRegisterDialogMediator实现具体联动规则ColleagueUIComponent 抽象类所有UI组件的基类ConcreteColleagueTextBox、Button 等具体组件// 抽象中介者interface DialogMediator {public void notify(UIComponent component, String event);}// 抽象同事类UI组件基类abstract class UIComponent {protected DialogMediator mediator;protected String name;public UIComponent(DialogMediator mediator, String name) { this.mediator mediator; this.name name; } public String getName() { return name; } // 组件状态变化时通知中介者 public void changed() { mediator.notify(this, changed); } public abstract void setEnabled(boolean enabled); public abstract String getValue();}// 具体同事类文本框class TextBox extends UIComponent {private String value “”;public TextBox(DialogMediator mediator, String name) { super(mediator, name); } public void setValue(String value) { this.value value; changed(); // 值变化时通知中介者 } public String getValue() { return value; } public void setEnabled(boolean enabled) { System.out.println(name (enabled ? 启用 : 禁用)); }}// 具体同事类按钮class Button extends UIComponent {private boolean enabled false;public Button(DialogMediator mediator, String name) { super(mediator, name); } public void setEnabled(boolean enabled) { this.enabled enabled; System.out.println(name (enabled ? 启用 : 禁用)); } public String getValue() { return String.valueOf(enabled); } public void click() { if (enabled) { System.out.println(name 被点击); mediator.notify(this, click); } }}// 具体中介者注册对话框class RegisterDialogMediator implements DialogMediator {private TextBox usernameBox;private TextBox passwordBox;private TextBox confirmBox;private Button registerButton;private Label messageLabel;public RegisterDialogMediator(TextBox usernameBox, TextBox passwordBox, TextBox confirmBox, Button registerButton, Label messageLabel) { this.usernameBox usernameBox; this.passwordBox passwordBox; this.confirmBox confirmBox; this.registerButton registerButton; this.messageLabel messageLabel; } public void notify(UIComponent component, String event) { // 用户名变化 → 检查是否已存在 if (component usernameBox event.equals(changed)) { String username usernameBox.getValue(); if (isUsernameExists(username)) { messageLabel.setText(用户名已存在); registerButton.setEnabled(false); } else { messageLabel.setText(); validateForm(); } } // 密码或确认密码变化 → 检查是否一致 else if ((component passwordBox || component confirmBox) event.equals(changed)) { String password passwordBox.getValue(); String confirm confirmBox.getValue(); if (!password.isEmpty() !confirm.isEmpty() !password.equals(confirm)) { messageLabel.setText(两次密码不一致); registerButton.setEnabled(false); } else { messageLabel.setText(); validateForm(); } } // 注册按钮点击 → 执行注册 else if (component registerButton event.equals(click)) { doRegister(); } } private void validateForm() { boolean valid !usernameBox.getValue().isEmpty() !passwordBox.getValue().isEmpty() passwordBox.getValue().equals(confirmBox.getValue()) !isUsernameExists(usernameBox.getValue()); registerButton.setEnabled(valid); } private boolean isUsernameExists(String username) { // 模拟检查用户名是否存在 return admin.equals(username); } private void doRegister() { System.out.println(注册成功 usernameBox.getValue()); }}关键点每个组件只知道自己状态通过 changed() 通知中介者中介者根据变化类型执行不同的联动规则如果联动规则变化如增加邮箱验证只需修改中介者组件类不变。MVC架构中的ControllerMVC架构中Controller作为中介者协调Model和View的交互避免Model和View直接耦合。场景用户列表页面View显示用户列表Model提供用户数据。用户点击删除按钮时View通知ControllerController调用Model删除用户Model返回结果后Controller更新View。角色对照MediatorUserControllerColleagueUserModel数据层、UserView视图层// Model用户数据同事类Aclass UserModel {private List users new ArrayList(Arrays.asList(“Alice”, “Bob”, “Charlie”));public ListString getUsers() { return new ArrayList(users); } public boolean deleteUser(String username) { return users.remove(username); }}// View用户视图同事类Bclass UserView {public void showUsers(List users) {System.out.println(“用户列表” users);}public void showMessage(String message) { System.out.println(提示 message); } // 用户点击删除按钮通知Controller public void onDeleteClicked(String username, UserController controller) { controller.handleDelete(username); }}// Controller中介者class UserController {private UserModel model;private UserView view;public UserController(UserModel model, UserView view) { this.model model; this.view view; } // 协调Model和View的交互 public void handleDelete(String username) { boolean success model.deleteUser(username); // 调用Model if (success) { view.showMessage(删除成功 username); // 更新View view.showUsers(model.getUsers()); // 刷新列表 } else { view.showMessage(删除失败 username 不存在); } } public void showUserList() { view.showUsers(model.getUsers()); }}关键点Model只负责数据操作不关心View如何显示View只负责显示和接收用户操作不关心Model如何处理数据Controller作为中介者协调两者Model和View通过Controller通信。多人聊天室聊天室是中介者模式的经典应用聊天室服务器作为中介者用户作为同事类用户之间不直接通信都通过聊天室转发消息。场景多人聊天室用户A发送消息时聊天室服务器转发给所有其他用户。用户加入或退出时聊天室通知所有用户。角色对照MediatorChatRoom 接口ConcreteMediatorConcreteChatRoom维护用户列表转发消息ColleagueUser 抽象类ConcreteColleagueChatUser具体用户实现// 抽象中介者interface ChatRoom {public void register(User user);public void send(String message, User sender);}// 抽象同事类用户基类abstract class User {protected ChatRoom chatRoom;protected String name;public User(ChatRoom chatRoom, String name) { this.chatRoom chatRoom; this.name name; } public String getName() { return name; } public abstract void receive(String message, String senderName); public abstract void send(String message);}// 具体中介者聊天室class ConcreteChatRoom implements ChatRoom {private List users new ArrayList();public void register(User user) { users.add(user); System.out.println(user.getName() 加入聊天室); // 通知其他用户 for (User u : users) { if (u ! user) { u.receive(user.getName() 加入了聊天室, 系统); } } } public void send(String message, User sender) { // 转发给除发送者外的所有用户 for (User u : users) { if (u ! sender) { u.receive(message, sender.getName()); } } }}// 具体同事类聊天用户class ChatUser extends User {public ChatUser(ChatRoom chatRoom, String name) {super(chatRoom, name);}public void receive(String message, String senderName) { System.out.println([ name 收到] senderName message); } public void send(String message) { System.out.println([ name 发送] message); chatRoom.send(message, this); // 通过中介者转发 }}// 使用示例public class ChatRoomDemo {public static void main(String[] args) {ChatRoom room new ConcreteChatRoom();User alice new ChatUser(room, Alice); User bob new ChatUser(room, Bob); User charlie new ChatUser(room, Charlie); room.register(alice); // Alice加入无其他用户 room.register(bob); // Bob加入通知Alice room.register(charlie);// Charlie加入通知Alice和Bob alice.send(大家好); // Alice发消息Bob和Charlie收到 bob.send(Hi Alice!); // Bob发消息Alice和Charlie收到 }}关键点用户之间不直接通信都通过聊天室转发降低耦合聊天室维护用户列表负责消息转发和用
