跳到主要内容

中介者模式

定义

中介者模式(mediator pattern) 定义了一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给中介对象交互,来避免对象之间的直接交互。

中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。 原来一个对象要跟 N 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码了的复杂度,提高代码的可读性和可维护性。 提到过中介者模式,有一个比较经典的例子就是航空管制。为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其它飞机每时每刻的位置,这就需要时刻跟其它飞机通信,发送自己的位置给塔台,由塔台来负责每架飞机的航线调度,这样就大大简化了通信网络。

模式原理

package com.e6yun.project.behavior.mediator.example1;

public class Client {

public static void main(String[] args) {
// 创建中介者
Mediator mediator = new MediatorImpl();

// 创建同事对象
Colleague colleagueA = new ConcreteColleagueA(mediator);
Colleague colleagueB = new ConcreteColleagueB(mediator);

colleagueA.exec("A");
colleagueB.exec("B");
}

}
package com.e6yun.project.behavior.mediator.example1;

/**
* 抽象同事类
* @author 21129
*/
public abstract class Colleague {
private Mediator mediator;

public Colleague(Mediator mediator) {
this.mediator = mediator;
}

public Mediator getMediator() {
return mediator;
}
// 同事间交互的抽象方法
public abstract void exec(String key);
}
package com.e6yun.project.behavior.mediator.example1;

/**
* 具体同事类
*/
public class ConcreteColleagueA extends Colleague {

public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}

@Override
public void exec(String key) {
System.out.println("---在 A 同事中,通过中介者执行!" + key);
getMediator().apply(key);
}
}
package com.e6yun.project.behavior.mediator.example1;

/**
* 具体同事类
*/
public class ConcreteColleagueB extends Colleague {

public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}

@Override
public void exec(String key) {
System.out.println("---在 B 同事中,通过中介者执行!" + key);
getMediator().apply(key);
}
}
package com.e6yun.project.behavior.mediator.example1;

/**
* 抽象中介者
*/
public interface Mediator {

// 处理同事对象注册与转发同事对象信息的方法。
void apply(String key);

}
package com.e6yun.project.behavior.mediator.example1;

/**
* 具体中介者,管理各个同事对象,并协调各个同事对象交互关系
*/
public class MediatorImpl implements Mediator {

@Override
public void apply(String key) {
System.out.println("最终中介者执行的操作 key:" + key);
}
}

聊天室路由示例

下面示例加入了用户注册、广播与私聊路由能力,更贴近真实业务需求,体现中介者在“集中协调交互”中的价值。

package com.e6yun.project.behavior.mediator.example1.biz;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

// 中介者接口:定义聊天相关的路由动作
public interface ChatMediator {
void register(User user);
void sendToAll(String fromUserId, String message);
void sendTo(String fromUserId, String toUserId, String message);
}

// 具体中介者:负责管理用户注册、消息广播和点对点转发
class ChatRoom implements ChatMediator {
private final Map<String, User> userIdToUser = new ConcurrentHashMap<>();

@Override
public void register(User user) {
if (user == null || user.getUserId() == null) {
throw new IllegalArgumentException("user or userId cannot be null");
}
userIdToUser.put(user.getUserId(), user);
}

@Override
public void sendToAll(String fromUserId, String message) {
userIdToUser.values().forEach(u -> u.receive(fromUserId, message));
}

@Override
public void sendTo(String fromUserId, String toUserId, String message) {
User target = userIdToUser.get(toUserId);
if (target != null) {
target.receive(fromUserId, message);
} else {
System.out.println("[WARN] target user not found: " + toUserId);
}
}
}

// 抽象同事:统一依赖 ChatMediator,不直接彼此引用
abstract class User {
private final String userId;
private final String nickname;
private final ChatMediator mediator;

protected User(String userId, String nickname, ChatMediator mediator) {
this.userId = userId;
this.nickname = nickname;
this.mediator = mediator;
this.mediator.register(this);
}

public String getUserId() { return userId; }
public String getNickname() { return nickname; }
protected ChatMediator getMediator() { return mediator; }

public abstract void receive(String fromUserId, String message);
}

// 具体同事:普通用户
class NormalUser extends User {
public NormalUser(String userId, String nickname, ChatMediator mediator) {
super(userId, nickname, mediator);
}

public void sayToAll(String content) {
getMediator().sendToAll(getUserId(), content);
}

public void sayTo(String toUserId, String content) {
getMediator().sendTo(getUserId(), toUserId, content);
}

@Override
public void receive(String fromUserId, String message) {
System.out.printf("[Broadcast@%s] from=%s, msg=%s%n", getNickname(), fromUserId, message);
}
}

// 具体同事:客服或机器人,可自定义接收逻辑
class ServiceBot extends User {
public ServiceBot(String userId, String nickname, ChatMediator mediator) {
super(userId, nickname, mediator);
}

@Override
public void receive(String fromUserId, String message) {
System.out.printf("[Bot@%s] 收到来自 %s 的消息:%s%n", getNickname(), fromUserId, message);
}
}

// 客户端:演示业务交互
class ChatClient {
public static void main(String[] args) {
ChatMediator chatRoom = new ChatRoom();

NormalUser alice = new NormalUser("u1", "Alice", chatRoom);
NormalUser bob = new NormalUser("u2", "Bob", chatRoom);
ServiceBot bot = new ServiceBot("b1", "HelperBot", chatRoom);

// 广播
alice.sayToAll("大家好,我是 Alice!");

// 私聊
bob.sayTo("u1", "Hi Alice,今晚一起学习设计模式?");

// 给机器人发消息
alice.sayTo("b1", "请给我中介者模式的概念");
}
}

中介者模式在聊天室路由中,通过 register 方法将用户注册到聊天室,并定义了 sendToAllsendTo 方法,分别用于广播和点对点消息。 简化了业务逻辑,将各个参与者的依赖关系集中到中介者中,并定义了统一接口,便于扩展新参与者。 坏处是引入了额外的中介者对象,增加了对象数量,增加了对象间依赖关系,增加了对象间的通信成本。

典型落地场景

  • 订单中心协调

    • 订单确认需协调库存预占、优惠核销、风控、支付、物流。
    • 通过中介者集中编排与路由,解耦子域依赖,便于扩展新参与者。
  • 通知/告警聚合平台

    • 统一对接短信、邮件、IM、Push,含重试、限流、降级与优先级。
    • 中介者按策略选择渠道并编排,统一失败处理与追踪。
  • 审批流/工单流转

    • 多角色、多条件分支、回退/加签、并行会签。
    • 中介者协调“节点处理器”,避免节点间强耦合,便于插拔节点。
  • BFF/聚合查询网关

    • 单接口聚合多个下游(用户、订单、账户、优惠等)。
    • 中介者按页面/场景编排下游调用,统一超时/熔断/降级。
  • 应用事件(领域事件)

    • 利用 Spring ApplicationEventPublisher + @EventListener 作为轻量中介者。
    • 发布方与订阅方解耦,适合积分发放、消息推送、埋点上报等旁路逻辑。
  • 命令总线/CQRS

    • Mediator.send(command) 路由到对应 CommandHandler,实现读写分离。
    • 控制层仅依赖中介者,新增命令仅新增处理器,符合开闭原则。

中介者模式 VS 观察者模式

观察者模式有多种实现方式,虽然经典的实现方式没法彻底解耦观察者和被观察者,观察者需要注册到被观察者中,被观察者状态更新时需要调用观察者的 update() 方法。

但是,在跨进程的实现方式中,我们可以利用消息队列实现彻底解耦,观察者和被观察者都只需要跟消息队列交互,观察者完全不需要知道被观察者的存在,被观察者也完全不知道观察者的存在。

中介者模式是为了解耦对象之间的交互,所有的参与者都只与中介者进行交互。而观察者模式中的消息队列,就有点类似于中介者模式中的 “中介”,观察者模式中的观察者和被观察者,就有点类似中介者模式中的 “参与者”。

那么问题来了:中介者模式和观察者模式的区别在哪?什么时候使用中介者模式?什么时候选择使用观察者模式?

在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,但部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式 的应用场景中,参与者之间的交互关系比较有条理。

而中介者模式正好相反。只有当参与者之间的交互关系错综复杂、维护成本很高的时候,才考虑使用中介者模式。毕竟中介者模式也会带来一定的副作用,可能会产生大而复杂的上帝类。 除此之外,如果一个参与者状态的改变,其它参与者执行的操作有一定先后顺序的要求,这个时候,中介者模式就可以利用中介类,通过先调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。

观察者模式和中介者模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景上,参与者之间的交互比较有条理,一般是单向的,一个参与者只有一个身份,要么是观察者、要么是被观察者。 而在中介者模式中,参与者之间的交互关系往往比较复杂,既可以是消息的发送者,也可以是消息的接收者。

总结

优点

  • 中介者模式简化了对象之间的交互,它用中介者和同事的一对多的交互关系替代了同事之间的多对多的交互,一对多关系更好理解,易于维护和扩展,将原本难以理解的网状结构转换成相对简单的星状结构。
  • 可以将各个同事就对象进行解耦,中介者有利于各个同事之间的松耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新的同时类都比较方便,更符合开闭原则。
  • 可以减少子类生成,中介者将原本分布于多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,使得同事类可以被重用,无需直接对同时类进行扩展。

缺点

  • 在具体中介者类中包含了大量同事之间的交互细节,可能会导致中介者类变得非常复杂,使得系统不好维护。

使用场景

  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  • 一个对象由于引用了其它的很多对象并且直接和这些对象进行通信,导致难以复用该对象。
  • 想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互的公共行为,如果需要改变行为则可以再增加新的中介类。