有个项目我接手的时候,Controller 层长这样:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired private OrderService orderService;
@Autowired private InventoryService inventoryService;
@Autowired private PaymentService paymentService;
@Autowired private NotificationService notificationService;
@Autowired private LogisticsService logisticsService;
@Autowired private CouponService couponService;
@Autowired private UserService userService;
@Autowired private AuditService auditService;
@PostMapping("/submit")
public Result submit(@RequestBody OrderDTO dto) {
// 每个 Service 之间还要传来传去
User user = userService.getById(dto.getUserId());
Coupon coupon = couponService.validate(dto.getCouponId(), user);
boolean hasStock = inventoryService.checkStock(dto.getItems());
if (!hasStock) return Result.fail("库存不足");
Order order = orderService.create(dto, user, coupon);
inventoryService.lock(dto.getItems());
PaymentResult pay = paymentService.pay(order, coupon);
notificationService.sendOrderConfirm(user, order);
logisticsService.createShipment(order);
auditService.record("ORDER_SUBMIT", user.getId(), order.getId());
return Result.ok(order);
}
}
八个 Service 注入,十几个调用步骤。每次产品加一个新流程——比如"下单成功后要发优惠券"——就得在这个 Controller 里再加一行。三个月后,这个方法长到了 200 行,没人敢动,也没人看得懂。
这就是典型的中介者模式缺失症。
不是代码写得烂,是架构少了一层
中介者模式(Mediator Pattern)解决的核心问题一句话就能说清楚:一堆对象互相通信导致网状依赖,引入一个中介者把它们变成星形依赖。
说人话:把 A 调 B、B 调 C、C 调 D 的乱麻,变成大家都只跟一个 M 聊,M 负责协调所有人。
很多工程师觉得中介者模式"太简单了没啥用"或者"就是个 EventBus"。但实际上你在日常写代码时已经反复遇到需要中介者的场景,只是你没意识到这是个设计模式问题。
聊天室:中介者最直观的教科书案例
假设你写一个聊天室,没有中介者的话:
public class User {
private String name;
private List<User> contacts; // 每个用户持有所有联系人
public void send(String msg) {
for (User contact : contacts) {
contact.receive(name, msg);
}
}
}
每个 User 都要知道所有其他 User,加人删人都得遍历更新。更恶心的是,如果你想加一个"敏感词过滤"功能,得在每个 User 的 send 方法里加——或者更糟,你得让 User 去依赖一个 Filter 类。
引入中介者:
public interface ChatMediator {
void sendMessage(User sender, String msg);
void addUser(User user);
}
public class ChatRoom implements ChatMediator {
private List<User> users = new ArrayList<>();
private SensitiveWordFilter filter = new SensitiveWordFilter();
public void sendMessage(User sender, String msg) {
String filtered = filter.check(msg);
for (User user : users) {
if (user != sender) {
user.receive(sender.getName(), filtered);
}
}
}
}
public class User {
private ChatMediator mediator; // 只认识中介者
private String name;
public void send(String msg) {
mediator.sendMessage(this, msg);
}
}
User 从认识所有人变成只认识 ChatRoom。加过滤、加日志、加持久化,全在 ChatRoom 里改,User 一行不动。
这就是中介者模式的价值:把 N×N 的依赖关系压缩成 N×1。
微服务编排:中介者模式的分布式版本
回到开头那个下单 Controller 的问题——那其实是一个微服务编排问题在单体里的预习。
在分布式系统里,下单流程涉及七八个服务,你有两种选择:
编排(Orchestration)——一个中心化的编排器协调所有服务。这就是中介者。
编排(Choreography)——每个服务自己监听事件、自己决定做什么。这是观察者。
两种方式没有绝对的好坏,但中介者(编排)有一个观察者没有的优势:你能在一个地方看到完整的业务流程。
// 编排器 = 中介者
public class OrderSagaOrchestrator {
public OrderResult submitOrder(OrderDTO dto) {
// Step 1: 验证用户
User user = userService.getById(dto.getUserId());
if (user == null) return fail("用户不存在");
// Step 2: 验证优惠券
if (dto.getCouponId() != null) {
CouponResult cr = couponService.validate(dto.getCouponId(), user.getId());
if (!cr.isValid()) return fail(cr.getReason());
}
// Step 3: 检查库存
InventoryResult ir = inventoryService.checkAndLock(dto.getItems());
if (!ir.isSuccess()) return fail("库存不足");
// Step 4: 创建订单
Order order = orderService.create(dto);
// Step 5: 支付
PaymentResult pr = paymentService.execute(order);
if (!pr.isSuccess()) {
// 补偿:释放库存
inventoryService.unlock(dto.getItems());
return fail("支付失败");
}
// Step 6: 后续操作
notificationService.sendConfirm(user, order);
logisticsService.createShipment(order);
couponService.markUsed(dto.getCouponId());
return Result.ok(order);
}
}
Orchestrator 把所有步骤集中管理,补偿逻辑写在一处,业务流程一目了然。
但这里有一个中介者模式的经典陷阱:中介者本身成了上帝类。
中介者不是上帝类:分层的艺术
直接用一个 Orchestrator 接管所有流程,三个月后这个类 500 行,也会变成新的噩梦。
解决思路是按领域拆分中介者:
// 不要让一个 Mediator 管所有事
public class OrderSubmitMediator {
private UserValidator userValidator;
private CouponValidator couponValidator;
private InventoryCoordinator inventoryCoordinator;
private PaymentCoordinator paymentCoordinator;
public Result submit(OrderDTO dto) {
// 每个子协调器处理自己的领域
userValidator.validate(dto.getUserId());
couponValidator.validate(dto.getCouponId(), dto.getUserId());
inventoryCoordinator.lock(dto.getItems());
// ...
}
}
把大中介者拆成多个小中介者,每个管一个子领域。这就是"中介者的中介者"——一种递归的组合。
另一个工程上的解法是用事件总线代替中介者:
public class OrderEventBus {
private Map<Class<?>, List<Consumer<Object>>> handlers = new HashMap<>();
public <T> void register(Class<T> eventType, Consumer<T> handler) {
handlers.computeIfAbsent(eventType, k -> new ArrayList<>())
.add((Consumer<Object>) handler);
}
public void publish(Object event) {
List<Consumer<Object>> consumers = handlers.get(event.getClass());
if (consumers != null) {
consumers.forEach(c -> c.accept(event));
}
}
}
// 使用
eventBus.register(OrderCreatedEvent.class, e -> {
notificationService.sendConfirm(e.getUser(), e.getOrder());
});
eventBus.register(OrderCreatedEvent.class, e -> {
logisticsService.createShipment(e.getOrder());
});
// Controller 只发布事件
orderService.create(dto);
eventBus.publish(new OrderCreatedEvent(order, user));
但事件总线的缺点是:你失去了流程的可见性。哪些 handler 会响应 OrderCreatedEvent?handler 之间的执行顺序是什么?这又回到了编排 vs 编排的经典取舍。
中介者给你流程可见性,事件总线给你解耦灵活性。选哪个取决于你的场景更需要哪一样。
Spring 里其实到处都是中介者
DispatcherServlet 某种程度上就是一个中介者——它自己不处理业务,但它协调 HandlerMapping、HandlerAdapter、ViewResolver 之间的交互。
// DispatcherServlet 的核心方法(简化)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
// 1. 根据 request 找 Handler
HandlerExecutionChain handler = getHandler(request);
// 2. 根据 Handler 找适配器
HandlerAdapter ha = getHandlerAdapter(handler.getHandler());
// 3. 执行前置拦截器
if (!handler.applyPreHandle(request, response)) return;
// 4. 实际调用 Handler
ModelAndView mv = ha.handle(request, response, handler.getHandler());
// 5. 执行后置拦截器
handler.applyPostHandle(request, response, mv);
// 6. 解析视图
processDispatchResult(request, response, handler, mv);
}
这里面 HandlerMapping、HandlerAdapter、ViewResolver 彼此不直接通信,全由 DispatcherServlet 这个中介者协调。这就是典型的中介者模式。
你不需要实现一个 ChatRoom 才算用了中介者——任何把多对多调用收敛到一对多调用的设计,都是中介者。
什么时候不该用中介者
说一个反直觉的事:不是所有网状依赖都需要中介者。
如果你的业务逻辑本身就很简单——比如只有两三个对象的协作——引入中介者反而增加了一层无意义的抽象。你本来 A→B→C 调用链很清晰,加个 Mediator 变成 A→M→B→M→C,写代码的人得多绕一圈。
另外,如果你的"对象之间的通信"本质上是数据流而不是控制流——比如一个数据管道 ETL 的场景——那更适合用管道-过滤器模式,而不是中介者。
回到那个 8 个 Service 的 Controller
我用中介者模式重构后,Controller 变成了:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired private OrderSubmitMediator mediator;
@PostMapping("/submit")
public Result submit(@RequestBody OrderDTO dto) {
return mediator.submit(dto);
}
}
Controller 只有一行。业务流程、补偿逻辑、异常处理全部在 Mediator 里。加了新流程也只需改 Mediator 或其子协调器。
代码行数没少——甚至可能多了——但每个类的职责清晰了。这就是设计模式的价值:不是为了少写代码,是为了让代码能被读懂、能被改动、能被测试。
对了,我正在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画故事 + 答题闯关的方式讲,中介者模式在里面的故事是一群动物吵架让卡皮巴拉来调解,如果你感兴趣可以搜一下,或者等我后面的文章,每个模式我都会同步对应的漫画内容。