你在 Controller 里注入 8 个 Service,其实是想请一个中介者

有个项目我接手的时候,Controller 层长这样:

java 复制代码
@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"。但实际上你在日常写代码时已经反复遇到需要中介者的场景,只是你没意识到这是个设计模式问题。

聊天室:中介者最直观的教科书案例

假设你写一个聊天室,没有中介者的话:

java 复制代码
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 类。

引入中介者:

java 复制代码
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)------每个服务自己监听事件、自己决定做什么。这是观察者。

两种方式没有绝对的好坏,但中介者(编排)有一个观察者没有的优势:你能在一个地方看到完整的业务流程。

java 复制代码
// 编排器 = 中介者
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 行,也会变成新的噩梦。

解决思路是按领域拆分中介者

java 复制代码
// 不要让一个 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());
        // ...
    }
}

把大中介者拆成多个小中介者,每个管一个子领域。这就是"中介者的中介者"------一种递归的组合。

另一个工程上的解法是用事件总线代替中介者:

java 复制代码
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 之间的交互。

java 复制代码
// 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 变成了:

java 复制代码
@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 个设计模式用漫画故事 + 答题闯关的方式讲,中介者模式在里面的故事是一群动物吵架让卡皮巴拉来调解,如果你感兴趣可以搜一下,或者等我后面的文章,每个模式我都会同步对应的漫画内容。

相关推荐
爱勇宝17 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒1 天前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码1 天前
通达OA核心类库TD类深度解析
后端
Gopher_HBo1 天前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志1 天前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..1 天前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects1 天前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰1 天前
一些基础和问题解决
后端
陈随易1 天前
前端项目部署只要30秒
前端·后端·程序员
YIAN1 天前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp