只有当多个Service存在双向、条件性、状态依赖的交叉调用时,才值得引入中介者模式;否则是过度设计。Facade适用于单向流程封装,中介者则处理含分支、回跳、状态感知的复杂协作流。

PHP 中 Service 层之间直接互相调用,控制器里堆满 $serviceA->doX()、$serviceB->handleY($result)、$serviceC->notify($id),这不是协调,是胶水代码。中介者模式在这里不解决“能不能用”,而是解决“值不值得引入”——答案很明确:**只有当多个 Service 存在双向、条件性、状态依赖的交叉调用时,才值得上中介者;否则就是过度设计。**
什么时候该考虑 ConcreteMediator 而不是写个 Facade 类?
Facade 是单向封装,比如把支付+库存+物流三步串成一个 OrderService::place();而中介者要处理的是“支付成功后,库存要扣减,但若库存不足则触发补货,补货完成再重试支付,同时通知风控系统暂停该用户下单权限”这类带分支、回跳、状态感知的协作流。
- 出现
if ($serviceA->isFailed()) { $serviceB->rollback(); $serviceC->alert(); }这类跨 Service 的判断逻辑,且不止一处 - 多个 Service 需要共享中间状态(如订单临时锁、审批上下文、会话级缓存键)
- 新增一个 Service(比如加了短信服务)导致你不得不改 3 个已有 Service 的方法签名或内部逻辑
-
Controller开始承担路由之外的职责:决定“下一步调哪个 Service”“传什么参数”“失败后走哪条补偿路径”
Mediator 接口设计必须避开这 3 个坑
很多实现把 send() 做成万能转发器,结果中介者变成巨型 switch —— 这违背了“集中控制”的初衷,反而更难维护。
- 不要定义
public function send(string $topic, mixed $payload): void。主题泛化会让类型安全和 IDE 提示失效,后期 debug 全靠var_dump() - 接口方法应按业务动词命名,比如
confirmOrder(int $orderId): void、rejectPayment(int $paymentId, string $reason): void,而不是统一用notify() - 避免让具体中介者直接持有所有 Service 实例(
$this->orderService,$this->stockService...)。改用依赖注入容器或策略映射表,否则每次加 Service 都得改构造函数和属性声明
ConcreteMediator 里怎么安全调用多个 Service?
关键不是“怎么调”,而是“谁负责错误传播和事务边界”。中介者本身不应开启事务,也不应吞掉异常。
立即学习“PHP免费学习笔记(深入)”;
- 所有 Service 方法需声明明确的返回类型,比如
ResultObject|false或抛出领域异常(StockShortageException,PaymentExpiredException),而非用try/catch静默处理 - 中介者内使用
match或策略数组分发,而非长 if-else 链。例如:$handlers = [ 'confirmOrder' => fn(int $id) => $this->orderService->confirm($id), 'reserveStock' => fn(int $id) => $this->stockService->reserve($id), ]; - 若涉及 DB 操作,事务应由最外层调用方(通常是 Controller 或应用服务)控制,中介者只负责编排;否则你会遇到嵌套事务、连接泄漏、回滚范围失控等问题
真正容易被忽略的点是生命周期——PHP 请求结束即销毁对象,所以中介者不能依赖实例变量保存跨方法状态(比如把 $this->pendingNotifications = [] 留到下一次 send() 调用)。所有中间状态必须显式传入、返回,或落库/Redis。否则看似解耦了 Service,实则把隐式依赖搬进了中介者内部。



















