还记得那次项目"灾难"吗?随着组件增多,代码改一处就得动十处,牵一发动全身。这种 N² 复杂度让维护变的很恶心:10 个组件需管理 45 个交互关系,20 个组件则是 190 个!中介者模式是救星,它通过一个中心化协调者将网状结构转为星状,每个组件只与中介者交互,复杂度降至 O(n),让代码重获清晰和秩序。
什么是中介者模式?
中介者模式是一种行为型设计模式,它通过引入一个中介对象来处理一组对象之间的交互,从而将对象之间的直接引用关系解耦,降低了系统的复杂度。
这种模式与"迪米特法则"(最少知识原则)密切相关,该法则建议一个对象应该尽量少地了解其他对象。中介者模式通过集中管理交互逻辑,让每个组件只需要了解中介者,而不需要了解其他组件的细节,完美践行了这一原则。
想象一下,中介者就像是一个交通指挥中心,各个组件就像是道路上的车辆。如果没有指挥中心,每辆车都需要与其他车辆直接沟通,这会导致混乱。而有了指挥中心后,车辆只需要与指挥中心通信,由指挥中心协调大家的行动。
中介者模式的适用场景
-
对象之间存在复杂的网状交互关系:当系统中的对象形成了错综复杂的引用关系时,使用中介者模式可以将这种网状结构转化为星状结构。
-
某个对象引用了多个对象并直接与之通信:可以通过引入中介者来减少对象之间的直接依赖。
-
多个组件间的协作行为需要集中控制:当组件之间的交互逻辑变得复杂,并且这些交互行为需要集中管理时。
-
想要创建可复用的组件,但又不希望这些组件与特定的类直接耦合:通过中介者接口可以降低组件的耦合度,提高复用性。例如,聊天室案例中的
User类只依赖于ChatMediator接口而非具体实现,因此可以在不同的聊天系统中复用,只需提供不同的中介者实现即可。 -
GUI 界面组件的交互管理:如按钮点击、文本框输入、下拉选择等操作需要影响其他界面元素时,可以使用中介者模式集中处理这些交互。
实际案例:聊天室系统
聊天室是中介者模式的经典应用场景。在聊天室中,用户之间不直接通信,而是通过聊天室(中介者)转发消息。
graph TD
A[聊天室: 中介者] --- B[用户1]
A --- C[用户2]
A --- D[用户3]
A --- E[用户4]
如果没有中介者,用户之间的通信线路会呈指数级增长:
graph TD
B[用户1] --- C[用户2]
B --- D[用户3]
B --- E[用户4]
C --- D
C --- E
D --- E
下面是一个聊天室的实现示例:
// 中介者接口
public interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// 具体中介者:聊天室
public class ChatRoom implements ChatMediator {
// 使用CopyOnWriteArrayList保证多线程环境下的线程安全
private List<User> users;
public ChatRoom() {
// 多线程环境中应该使用:
// this.users = new CopyOnWriteArrayList<>();
// 简单示例中使用ArrayList即可
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
this.users.add(user);
user.setMediator(this); // 中介者反向设置自身,维持一致性
}
@Override
public void sendMessage(String message, User sender) {
// 将消息发送给除了发送者之外的所有用户
for (User user : users) {
// 使用equals比较而非引用比较,更安全,尤其在分布式或序列化场景
if (!user.equals(sender)) {
user.receive(message);
}
}
}
}
// 用户抽象类
public abstract class User {
protected ChatMediator mediator;
protected String name;
public User(String name) {
this.name = name;
}
// 设置中介者,由中介者主动调用
public void setMediator(ChatMediator mediator) {
this.mediator = mediator;
}
// 显式声明抽象方法,子类必须实现发送消息的逻辑
public abstract void send(String message);
// 显式声明抽象方法,子类必须实现接收消息的逻辑
public abstract void receive(String message);
// 当状态变更需要通知中介者时调用
protected void notifyMediator() {
// 在聊天室例子中不需要主动通知,消息发送时已调用中介者
// 实际应用中可在此通知中介者状态变化
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
// 普通用户
public class NormalUser extends User {
public NormalUser(String name) {
super(name);
}
@Override
public void send(String message) {
System.out.println(this.name + " 发送消息: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " 收到消息: " + message);
}
}
// 客户端测试代码
public class ChatDemo {
public static void main(String[] args) {
ChatMediator chatroom = new ChatRoom();
User user1 = new NormalUser("张三");
User user2 = new NormalUser("李四");
User user3 = new NormalUser("王五");
// 中介者统一管理组件关系
chatroom.addUser(user1);
chatroom.addUser(user2);
chatroom.addUser(user3);
user1.send("大家好!");
user3.send("你好,张三!");
}
}
运行结果:
张三 发送消息: 大家好!
李四 收到消息: 大家好!
王五 收到消息: 大家好!
王五 发送消息: 你好,张三!
张三 收到消息: 你好,张三!
李四 收到消息: 你好,张三!
另一个实例:航空管制系统
航空管制系统是中介者模式的另一个典型应用。在这个系统中,塔台作为中介者,协调各个飞机的起飞和降落。
sequenceDiagram
participant 飞机1
participant 塔台
participant 飞机2
飞机1->>塔台: 请求起飞
塔台->>飞机1: 允许起飞
飞机2->>塔台: 请求降落
塔台->>飞机2: 允许降落
代码实现:
// 定义操作类型枚举,提高代码语义清晰度
public enum OperationType {
LANDING("着陆"),
TAKEOFF("起飞");
private String description;
OperationType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// 中介者接口
public interface AirTrafficControl {
void addAircraft(Aircraft aircraft);
void requestLanding(Aircraft aircraft);
void requestTakeoff(Aircraft aircraft);
}
// 具体中介者:塔台
public class ControlTower implements AirTrafficControl {
private List<Aircraft> aircrafts;
private Queue<Aircraft> landingQueue;
private Queue<Aircraft> takeoffQueue;
public ControlTower() {
this.aircrafts = new ArrayList<>();
// 使用队列而非其他数据结构,确保请求按FIFO顺序处理
// 这对航空管制至关重要,保证"先请求先服务"的公平性
this.landingQueue = new LinkedList<>();
this.takeoffQueue = new LinkedList<>();
}
@Override
public void addAircraft(Aircraft aircraft) {
aircrafts.add(aircraft);
aircraft.setAtc(this); // 中介者反向设置自身引用
System.out.println("塔台: 飞机 " + aircraft.getCallSign() + " 已注册");
}
@Override
public void requestLanding(Aircraft aircraft) {
landingQueue.offer(aircraft);
System.out.println("塔台: 收到 " + aircraft.getCallSign() + " 的着陆请求");
processQueue(landingQueue, OperationType.LANDING);
}
@Override
public void requestTakeoff(Aircraft aircraft) {
takeoffQueue.offer(aircraft);
System.out.println("塔台: 收到 " + aircraft.getCallSign() + " 的起飞请求");
processQueue(takeoffQueue, OperationType.TAKEOFF);
}
// 提取通用的队列处理方法,使用枚举替代布尔值,提高可读性
private void processQueue(Queue<Aircraft> queue, OperationType operation) {
if (!queue.isEmpty()) {
Aircraft aircraft = queue.poll();
System.out.println("塔台: 允许 " + aircraft.getCallSign() + " " + operation.getDescription());
if (operation == OperationType.LANDING) {
aircraft.land();
} else {
aircraft.takeoff();
}
}
}
}
// 飞机抽象类
public abstract class Aircraft {
protected AirTrafficControl atc;
protected String callSign;
// 构造时不直接设置ATC,由中介者管理注册
public Aircraft(String callSign) {
this.callSign = callSign;
}
// 仅负责设置内部引用,不主动注册
public void setAtc(AirTrafficControl atc) {
this.atc = atc;
}
public String getCallSign() {
return callSign;
}
public void requestLanding() {
System.out.println(callSign + ": 请求着陆");
atc.requestLanding(this);
}
public void requestTakeoff() {
System.out.println(callSign + ": 请求起飞");
atc.requestTakeoff(this);
}
public abstract void land();
public abstract void takeoff();
}
// 客机
public class Airliner extends Aircraft {
public Airliner(String callSign) {
super(callSign);
}
@Override
public void land() {
System.out.println(callSign + ": 客机正在着陆");
}
@Override
public void takeoff() {
System.out.println(callSign + ": 客机正在起飞");
}
}
// 测试代码
public class ATCDemo {
public static void main(String[] args) {
AirTrafficControl tower = new ControlTower();
Aircraft flight1 = new Airliner("CA1234");
Aircraft flight2 = new Airliner("MU5678");
// 中介者负责管理组件,而非组件自行注册
tower.addAircraft(flight1);
tower.addAircraft(flight2);
flight1.requestTakeoff();
flight2.requestLanding();
}
}
运行结果:
塔台: 飞机 CA1234 已注册
塔台: 飞机 MU5678 已注册
CA1234: 请求起飞
塔台: 收到 CA1234 的起飞请求
塔台: 允许 CA1234 起飞
CA1234: 客机正在起飞
MU5678: 请求着陆
塔台: 收到 MU5678 的着陆请求
塔台: 允许 MU5678 着陆
MU5678: 客机正在着陆
GUI 组件交互的中介者模式
在 GUI 应用中,中介者模式尤为常见。想象一个登录表单,用户名、密码输入框和登录按钮需要协同工作:
// 中介者接口
public interface FormMediator {
void componentChanged(UIComponent component);
void registerComponent(UIComponent component);
}
// UI组件抽象类
public abstract class UIComponent {
protected FormMediator mediator;
public void setMediator(FormMediator mediator) {
this.mediator = mediator;
}
// 当组件状态变化时通知中介者,更清晰的命名
protected void notifyMediator() {
if (mediator != null) {
mediator.componentChanged(this);
}
}
}
// 具体组件:用户名输入框
public class UsernameField extends UIComponent {
private String username = "";
public void setUsername(String username) {
this.username = username;
notifyMediator(); // 通知中介者状态已变更
}
public String getUsername() {
return username;
}
}
// 具体组件:密码输入框
public class PasswordField extends UIComponent {
private String password = "";
public void setPassword(String password) {
this.password = password;
notifyMediator(); // 通知中介者状态已变更
}
public String getPassword() {
return password;
}
}
// 具体组件:登录按钮
public class LoginButton extends UIComponent {
private boolean enabled = false;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public void click() {
if (enabled) {
System.out.println("执行登录操作...");
}
}
}
// 具体中介者:登录表单
public class LoginFormMediator implements FormMediator {
private UsernameField usernameField;
private PasswordField passwordField;
private LoginButton loginButton;
// 通用组件注册方法,简化注册流程
@Override
public void registerComponent(UIComponent component) {
if (component instanceof UsernameField) {
this.usernameField = (UsernameField) component;
} else if (component instanceof PasswordField) {
this.passwordField = (PasswordField) component;
} else if (component instanceof LoginButton) {
this.loginButton = (LoginButton) component;
}
component.setMediator(this);
}
@Override
public void componentChanged(UIComponent component) {
// 根据用户名和密码是否都已输入来启用或禁用登录按钮
if (component instanceof UsernameField || component instanceof PasswordField) {
boolean bothFilled = !usernameField.getUsername().isEmpty()
&& !passwordField.getPassword().isEmpty();
loginButton.setEnabled(bothFilled);
System.out.println("登录按钮状态: " + (bothFilled ? "启用" : "禁用"));
}
}
}
// 测试代码
public class LoginFormDemo {
public static void main(String[] args) {
// 创建组件
UsernameField usernameField = new UsernameField();
PasswordField passwordField = new PasswordField();
LoginButton loginButton = new LoginButton();
// 创建中介者并关联组件
FormMediator mediator = new LoginFormMediator();
mediator.registerComponent(usernameField);
mediator.registerComponent(passwordField);
mediator.registerComponent(loginButton);
// 模拟用户输入
System.out.println("用户输入用户名...");
usernameField.setUsername("admin");
System.out.println("用户输入密码...");
passwordField.setPassword("123456");
// 尝试点击登录
loginButton.click();
}
}
运行结果:
用户输入用户名...
登录按钮状态: 禁用
用户输入密码...
登录按钮状态: 启用
执行登录操作...
在这个例子中,中介者模式使得各个 UI 组件不需要直接引用彼此。只有中介者知道"当用户名和密码都填写后才能启用登录按钮"这一业务规则,组件本身只负责通知中介者自己的状态变化。这样的设计使 UI 组件可以轻松复用在其他表单中。
中介者模式在 Spring 框架中的应用
Spring 框架中的事件机制就是中介者模式的一种应用。ApplicationContext(实现了 ApplicationEventPublisher 接口)作为中介者,协调各个 bean 之间的事件发布与监听,避免了事件发布者与监听者之间的直接耦合。
// 事件类
public class UserCreatedEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private String username;
public UserCreatedEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
// 事件发布者
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher publisher; // ApplicationContext实现了此接口
public void createUser(String username) {
System.out.println("创建用户: " + username);
// 发布者无需关心谁在监听,只负责发布事件
publisher.publishEvent(new UserCreatedEvent(this, username));
}
}
// 事件监听者
@Component
public class EmailService {
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
System.out.println("发送欢迎邮件给: " + event.getUsername());
}
}
// 异步事件监听者
@Component
public class LogService {
@EventListener
@Async // 标记为异步处理,不阻塞主线程
public void handleUserCreated(UserCreatedEvent event) {
System.out.println("记录用户创建日志: " + event.getUsername());
}
}
在这个例子中,UserService 不需要直接引用 EmailService 和 LogService,而是通过 ApplicationContext 这个中介者来协调它们的交互。Spring 的事件机制背后实际上是一个典型的发布-订阅模型,其中:
ApplicationEventPublisher接口作为中介者 API,允许组件发布事件ApplicationContext实现该接口,内部维护了事件与监听者的映射- 监听器注册机制:
- 实现
ApplicationListener接口的 Bean 会自动注册为监听器 @EventListener注解标记的方法会被 Spring 通过适配器转换为监听器- Spring 内部负责事件的多播分发,实际承担中介者的核心功能
值得注意的是,Spring 还支持异步事件处理(通过@Async注解),让中介者可以协调异步交互,进一步降低系统耦合。
中介者模式的不同形式
简化中介者(无接口)
在小型应用或组件间关系相对稳定的情况下,可以省略中介者接口,直接使用具体中介者:
// 简化中介者(无接口)
public class SimpleDialogMediator {
private Button okButton;
private Button cancelButton;
private Checkbox checkbox;
// 直接引用具体组件,无需接口抽象
public void registerComponent(Object component) {
if (component instanceof Button) {
if ("OK".equals(((Button)component).getText())) {
this.okButton = (Button)component;
} else {
this.cancelButton = (Button)component;
}
} else if (component instanceof Checkbox) {
this.checkbox = (Checkbox)component;
}
}
// 处理组件通知
public void componentClicked(Object component) {
if (component == okButton) {
// 处理确定按钮点击
} else if (component == cancelButton) {
// 处理取消按钮点击
} else if (component == checkbox) {
// 处理复选框状态变化
}
}
}
这种简化方式牺牲了一定的扩展性和灵活性,但在简单场景下可以减少代码量。
分布式协调器
在微服务架构中,API 网关可以视为一种分布式协调器,协调不同服务间的交互:
// API网关作为分布式协调器
@RestController
public class ApiGateway {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@PostMapping("/api/orders")
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
// 验证用户
UserDto user = userService.validateUser(request.getUserId());
// 创建订单
OrderDto order = orderService.createOrder(user, request.getItems());
// 处理支付
PaymentDto payment = paymentService.processPayment(order, request.getPaymentInfo());
// 返回组合结果
return ResponseEntity.ok(new OrderResponse(order, payment));
}
}
在分布式系统中,API 网关需要定义清晰的接口契约(如 REST API 规范),避免成为服务间交互的紧耦合点。良好的接口设计应该隐藏内部实现细节,只暴露必要的操作和参数。
中介者模式 vs 观察者模式
中介者模式和观察者模式都用于解耦对象之间的交互,但它们有着本质的区别:
| 特性 | 中介者模式 | 观察者模式 |
|---|---|---|
| 交互方式 | 中心化控制(组件通过中介者交互) | 广播式通知(主题向所有观察者发送通知) |
| 通信方向 | 多向(任何组件都可以通过中介者与其他组件交互) | 单向(主题向观察者通知) |
| 组件关系 | 组件不知道其他组件的存在 | 观察者知道主题,但主题不一定知道具体观察者 |
| 控制流 | 组件 → 中介者 → 组件(双向控制) | 主题 → 观察者(单向通知) |
| 职责边界 | 负责协调多个组件的复杂交互逻辑(如过滤、顺序控制) | 仅负责单向通知,不处理观察者之间的协作 |
| 控制复杂度 | 中介者可能变得复杂 | 主题相对简单,复杂性分散在各观察者中 |
| 适用场景 | 组件间有复杂的交互逻辑 | 一对多的依赖关系,当一个对象状态改变时通知其他对象 |
场景对比示例
聊天室(适合中介者模式):
// 中介者模式:用户之间通过聊天室交互
public class ChatRoom {
public void sendMessage(String message, User sender) {
// 中介者知道如何将消息从一个用户传递给其他用户
for (User user : userList) {
if (!user.equals(sender)) {
user.receive(message);
}
}
}
}
股票行情推送(适合观察者模式):
// 观察者模式:股票价格变化时通知所有订阅者
public class StockMarket {
private List<StockObserver> observers = new ArrayList<>();
public void addObserver(StockObserver observer) {
observers.add(observer);
}
public void updatePrice(String stock, double price) {
// 股票价格变动时,主题向所有观察者广播通知
for (StockObserver observer : observers) {
observer.update(stock, price);
}
}
}
核心区别:中介者知道组件之间如何交互并控制交互流程,而观察者模式中的主题只负责单向通知,不关心观察者之间的关系。
中介者模式的优缺点
优点
- 降低了系统的耦合度:各组件之间不再直接交互,而是通过中介者进行。
- 简化了对象之间的交互:将多对多的交互转变为一对多的交互。
- 提高了系统的可维护性:各组件的修改不会影响其他组件。
- 符合开闭原则:增加新组件只需要修改中介者,而不影响现有组件。
缺点
- 中介者可能变得过于庞大:随着系统规模的扩大,中介者可能会过于复杂,难以维护,尤其是当交互逻辑包含复杂业务规则或跨领域协作时。
判断中介者是否过于臃肿的标准:
- 代码行数超过 300 行
- 包含超过 10 个以上的条件分支(if-else/switch)
- 处理 3 个以上不同领域的业务逻辑
- 方法参数过多(超过 4 个)
- 把交互复杂性转移到了中介者:虽然降低了组件之间的耦合,但增加了中介者的复杂度。
如何避免中介者臃肿
下面是一个示例,展示如何拆分臃肿的聊天室中介者:
// 将ChatRoom拆分为两个职责明确的中介者
// 1. 消息中介者 - 负责消息传递
public interface MessageMediator {
void sendMessage(String message, User sender);
}
// 2. 用户管理中介者 - 负责用户管理
public interface UserMediator {
void addUser(User user);
void removeUser(User user);
}
// 组合实现
public class ChatRoomManager implements MessageMediator, UserMediator {
private MessageHandler messageHandler = new MessageHandler();
private UserManager userManager = new UserManager();
@Override
public void sendMessage(String message, User sender) {
// 委托给专门的消息处理器
messageHandler.sendMessage(message, sender, userManager.getUsers());
}
@Override
public void addUser(User user) {
// 委托给专门的用户管理器
userManager.addUser(user);
user.setMediator(this); // 保持双向关联
}
@Override
public void removeUser(User user) {
userManager.removeUser(user);
}
// 可以通过构造函数注入不同的处理器
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
}
// 消息处理策略
public class MessageHandler {
public void sendMessage(String message, User sender, List<User> users) {
for (User user : users) {
if (!user.equals(sender)) {
user.receive(message);
}
}
}
}
// 用户管理
public class UserManager {
private List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
public List<User> getUsers() {
return Collections.unmodifiableList(users);
}
}
这种拆分方式遵循以下原则:
- 单一职责:每个中介者只负责一类交互逻辑
- 组合优于继承:使用组合方式集成多个专门的处理器
- 策略模式结合:将变化的部分(如消息处理逻辑)抽取为策略类
中介者模式的使用建议
-
中介者职责单一:中介者应只负责协调组件交互,不应包含组件自身的业务逻辑。
-
通过接口依赖:组件应依赖中介者接口而非具体实现,保持松耦合。例如:
// 错误方式:组件直接依赖具体中介者
public class Button {
private LoginFormMediator mediator; // 直接依赖具体实现
}
// 正确方式:组件依赖中介者接口
public class Button {
private FormMediator mediator; // 依赖接口
public void setMediator(FormMediator mediator) {
this.mediator = mediator;
}
}
-
避免双向强依赖:组件和中介者之间的关系应该清晰,中介者负责管理组件,而组件只与中介者接口交互,不应了解中介者内部实现细节。
-
适度使用:不是所有组件交互都需要中介者,对于简单的一对一交互,直接引用可能更高效。
-
测试策略:由于中介者集中了交互逻辑,应通过全面的单元测试覆盖所有组件交互场景,包括边界条件和异常情况。测试中应该:
- 模拟各种组件状态变化
- 验证中介者的正确响应
- 检查交互顺序是否符合预期
- 确保中介者能处理异常情况
- 结合其他设计模式:
- 使用工厂模式创建中介者实例
- 使用单例模式确保中介者的唯一性
- 使用命令模式封装交互请求
- 使用策略模式处理不同的交互策略
总结
| 项目 | 内容 |
|---|---|
| 模式名称 | 中介者模式 |
| 类型 | 行为型设计模式 |
| 核心思想 | 通过中介者对象集中处理对象之间的交互,降低耦合度 |
| 主要角色 | 中介者接口(Mediator)、具体中介者(ConcreteMediator)、同事接口(Colleague)、具体同事类(ConcreteColleague) |
| 同事接口 | 定义同事类与中介者交互的接口,通常包含 setMediator()方法及状态变更通知方法(如 notifyMediator()) |
| 适用场景 | 对象之间存在复杂交互关系;需要集中控制组件间协作;GUI 组件交互 |
| 优点 | 降低耦合度;简化交互;提高可维护性和可扩展性 |
| 缺点 | 中介者可能过于庞大复杂;转移了复杂性而非消除 |
| 常见应用 | 聊天室、航空管制系统、GUI 框架、MVC 架构的控制器、消息中间件、API 网关 |