中介者模式:设备软件里多个手臂抢一个 Buffer,怎么协调才不乱

📅 2026/8/25 12:16:42
中介者模式:设备软件里多个手臂抢一个 Buffer,怎么协调才不乱
这篇解决一个问题设备软件里多个模块要互相协作怎么写才不让它们直接互调、互查状态而是通过一个中间人统一调度。从最直觉的写法开始一步步讲清中介者模式和外观模式的区别最后落到设备软件的真实场景。一、先从一个生活例子说起机场塔台。一架飞机要降落它不能直接跟其他飞机说「你们让开我要降」。它也不知道跑道现在有没有别的飞机。它只做一件事呼叫塔台。塔台知道所有飞机的位置、跑道状态、天气。塔台决定谁降、谁等、谁绕。飞机之间不直接通信全通过塔台。如果飞机之间直接通信呢十架飞机互相问「你在哪」「你要降吗」「跑道空吗」——信息爆炸谁也搞不清状况。塔台就是中介者。中介者的本质多个对象之间不直接通信全通过一个中间人协调。每个对象只跟中间人说话中间人知道所有人、统一决策。二、回到代码不用中介者会怎样设备软件里最典型的场景两只手臂左 Tray 手、右 Tray 手共用一组 Buffer。它们要协调左手取 Buffer1 时右手不能也取 Buffer1。左手取完通知右手可以取了。Buffer 满了通知两只手别再放了。Buffer 空了通知两只手可以来取了。最直觉的写法手臂之间直接互调class Arm { Arm* m_otherArm; // 持有对方手臂指针 Buffer* m_buffer; // 持有 Buffer 指针 public: void WorkFlow() { // 取之前先问对方在不在用 if (m_otherArm-IsBusyWith(m_buffer)) { Sleep(10); return; // 对方在用等 } // 占住 Buffer m_buffer-SetUsedBy(this); // 取料 PickStation(m_buffer); // 通知对方我用完了 m_otherArm-NotifyBufferFree(m_buffer); } };能跑但问题一堆。问题一耦合网状两只手还好四只手呢每只手要持有另外三只手的指针。N 只手就是 N×(N-1) 个指针。加一只新手所有已有手都要加一个指针指向它。Arm1 ──→ Arm2 Arm1 ──→ Arm3 Arm1 ──→ Arm4 Arm2 ──→ Arm1 Arm2 ──→ Arm3 Arm2 ──→ Arm4 ... N×(N-1) 条线问题二逻辑散落「谁该取」「谁该等」的判断逻辑分散在每只手的WorkFlow里。每只手都在自己判断「对方忙不忙」「Buffer 空不空」。改协调规则要改所有手。问题三死锁左手等右手释放 Buffer右手等左手释放另一个 Buffer。互相等死锁。问题四加新模块要改所有老模块加一只手臂所有已有手臂都要加一个m_pNewArm指针都要在WorkFlow里加判断。问题五测试困难测一只手臂要 mock 所有其他手臂和 Buffer。互相依赖扯出一大片。根本问题模块之间直接通信形成了网状耦合。N 个模块就是 N² 条关系线。三、中介者模式怎么解决思路把所有协调逻辑收拢到一个中间人。模块只跟中间人通信模块之间互不认识。第一步定义中介者接口class IBufferMediator { public: // 手臂请求取 Buffer virtual bool RequestPick(Arm* arm, Buffer* buf) 0; // 手臂报告取完了 virtual void ReportPickDone(Arm* arm, Buffer* buf) 0; // 手臂请求放 Buffer virtual bool RequestPlace(Arm* arm, Buffer* buf) 0; // 手臂报告放完了 virtual void ReportPlaceDone(Arm* arm, Buffer* buf) 0; virtual ~IBufferMediator() default; };第二步实现具体中介者class BufferMediator : public IBufferMediator { vectorBuffer* m_buffers; mapBuffer*, Arm* m_pickOwner; // 谁在取 mapBuffer*, Arm* m_placeOwner; // 谁在放 public: void AddBuffer(Buffer* buf) { m_buffers.push_back(buf); } bool RequestPick(Arm* arm, Buffer* buf) override { // 检查有没有别人在取这个 Buffer auto it m_pickOwner.find(buf); if (it ! m_pickOwner.end() it-second ! arm) return false; // 被别人占着 m_pickOwner[buf] arm; return true; } void ReportPickDone(Arm* arm, Buffer* buf) override { m_pickOwner.erase(buf); // 可以在这里通知其他手臂Buffer 空了 } bool RequestPlace(Arm* arm, Buffer* buf) override { auto it m_placeOwner.find(buf); if (it ! m_placeOwner.end() it-second ! arm) return false; m_placeOwner[buf] arm; return true; } void ReportPlaceDone(Arm* arm, Buffer* buf) override { m_placeOwner.erase(buf); } };第三步手臂只跟中介者通信class Arm { IBufferMediator* m_mediator; // 只认识中介者不认识其他手臂 public: void SetMediator(IBufferMediator* m) { m_mediator m; } void WorkFlow() { // 想取 Buffer问中介者 if (!m_mediator-RequestPick(this, m_targetBuffer)) { Sleep(10); return; // 中介者说不行等 } PickStation(m_targetBuffer); // 取完了告诉中介者 m_mediator-ReportPickDone(this, m_targetBuffer); } };第四步初始化时装配BufferMediator mediator; mediator.AddBuffer(buf1); mediator.AddBuffer(buf2); armLeft-SetMediator(mediator); armRight-SetMediator(mediator);关键变化手臂不持有其他手臂的指针。手臂不判断「对方忙不忙」只问中介者「我能取吗」。协调规则全在中介者里改规则只改中介者。加一只手臂只需要newArm-SetMediator(mediator)老手臂不改。四、中介者模式的标准结构角色例子Mediator中介者接口IBufferMediator定义协调接口ConcreteMediator具体中介者BufferMediator知道所有模块实现协调规则Colleague同事基类Arm持有中介者指针只跟中介者通信ConcreteColleague具体同事ArmTray/ArmBIB各自干自己的活ArmLeft ──→ Mediator ←── ArmRight│Buffer1, Buffer2核心同事之间不直接通信全通过中介者。N 个模块只有 N 条线都连到中介者不是 N² 条。五、中介者模式解决了什么问题不用中介者用中介者耦合N×N 网状N 条线连到中介者协调逻辑散落各模块集中在中介者加模块改所有老模块只注册到中介者死锁互相等中介者统一分配避免循环测试mock 所有其他模块只 mock 中介者一句话中介者把「模块间互相协调」变成「模块只跟中介者说话」从网状耦合变成星形耦合。六、设备软件里的真实场景场景一多手臂抢 Buffer前面讲的例子。左右 Tray 手、BIB 手都要访问 Buffer。用中介者统一分配手臂之间不互调。场景二上料站和手臂的交接上料站盘到位后要通知手臂来取手臂取完要通知上料站可以进下一盘。不用中介者的话上料站持有手臂指针手臂持有上料站指针双向依赖。class LoadMediator { Arm* m_arm; LoadStation* m_station; public: void OnTrayReady() { // 料道到位了通知手臂 m_arm-StartPick(); } void OnPickDone() { // 手臂取完了通知料道进下一盘 m_station-MoveToNext(); } };上料站和手臂互不认识都只跟中介者说话。场景三测试台和手臂的交接测试台测完要通知手臂来取手臂取走后要通知测试台可以放下一个料。同样的双向协调用中介者收拢。场景四整机流程调度更宏观地看整个设备的流程就是一个大中介者class MachineFlow { Arm* m_arm; Channel* m_channel; Buffer* m_buffer; Tester* m_tester; public: void OnChannelReady() { // 料道准备好让手臂取 m_arm-PickFrom(m_channel); } void OnArmPickDone() { // 手臂取完让 Buffer 接 m_buffer-PrepareReceive(); } void OnBufferReceived() { // Buffer 接到了让手臂放 m_arm-PlaceTo(m_buffer); } void OnBufferFull() { // Buffer 满了让 BIB 手取走 m_bibArm-PickFrom(m_buffer); } };所有模块只跟MachineFlow通信模块之间互不认识。协调规则全在MachineFlow里改流程只改中介者。七、中介者 vs 外观到底什么区别这两个模式都涉及「一个中间层管多个模块」最容易混。但区别很明确外观模式中介者模式方向单向外观调子系统双向子系统也调中介者目的简化调用入口解耦模块间通信子系统间互不知道不通信互不直接通信通过中介者谁主动Client 调外观子系统主动报告中介者中介者主动调子系统典型StopAllActors多手臂协调抢 Buffer一句话外观是「你找它帮忙批量操作」中介者是「模块之间要沟通都找它」。外观单向中介者双向。举例区分// 外观总控调外观子系统不调外观Actor::StopAllActors(); // 总控调子系统不主动调// 中介者子系统主动调中介者m_mediator-RequestPick(this, buf); // 手臂主动请求m_mediator-ReportPickDone(this, buf); // 手臂主动报告八、中介者 vs 观察者什么关系这两个也容易混因为都涉及「事件通知」。观察者模式中介者模式谁知道谁事件源不认识观察者中介者认识所有模块通信方向单向广播双向协调逻辑在哪观察者各自处理中介者集中决策适合事件通知报警流程协调抢资源关系中介者内部经常用观察者来收事件。模块发事件给中介者观察者中介者做决策后调其他模块。两者可以组合使用。九、中介者模式的坑坑一中介者变成上帝对象// 错误中介者里塞了所有逻辑 class GodMediator { void OnChannelReady() { /* 50行 */ } void OnArmPickDone() { /* 50行 */ } void OnBufferFull() { /* 50行 */ } void OnTestDone() { /* 50行 */ } void OnAlarm() { /* 50行 */ } // ... 500 行 };中介者知道太多、做太多变成新的上帝对象。把分散的耦合变成了集中的臃肿。对策按职责拆多个中介者。Buffer 协调一个、测试流程一个、报警一个。不要一个中介者管所有事。坑二中介者和子系统双向依赖class Mediator { Arm* m_arm; // 中介者认识手臂 }; class Arm { Mediator* m_mediator; // 手臂认识中介者 };双向依赖本身是中介者模式的特点但要注意循环引用导致的析构问题。对策用接口解耦。手臂依赖IMediator接口而不是具体中介者类。坑三中介者里做太多硬件操作// 错误中介者里直接调运动 class Mediator { void OnArmPickDone() { m_arm-MoveTo(safePos); // 中介者里调运动细节 m_buffer-OpenDoor(); // 中介者里调硬件 } };中介者应该只做「协调决策」谁该干什么不做「具体操作」怎么运动、怎么开门。具体操作由模块自己执行。对策中介者只调模块的高层接口PickFrom、PlaceTo不调底层细节MoveTo、OpenDoor。坑四过度中介化不是所有模块间通信都需要中介者。两个模块简单协作直接互调就行不必抽中介者。// 不需要中介者只有两个模块简单协作arm-PickFrom(channel); // 直接调清晰// 需要中介者三个以上模块协调复杂mediator-OnChannelReady(); // 中介者统一调度判断标准参与协调的模块超过三个、协调规则会变、加模块要改老模块时才用中介者。十、可复用结论中介者模式的本质多个模块之间不直接通信全通过一个中间人协调。从网状耦合变成星形耦合。四个角色Mediator接口、ConcreteMediator实现协调规则、Colleague模块基类、ConcreteColleague具体模块。设备软件最典型应用多手臂抢 Buffer、上料站和手臂交接、测试台和手臂交接、整机流程调度。核心价值加模块只注册中介者不改老模块协调规则集中在一处模块间互不认识。和外观的区别外观单向调子系统中介者双向子系统也调中介者。外观简化入口中介者解耦通信。和观察者的关系中介者内部可用观察者收事件两者可组合。坑别让中介者变上帝对象按职责拆、中介者只做决策不做硬件操作、超过三个模块协调才用。中介者模式在设备软件里不是「课本上的 Mediator」而是「多手臂抢 Buffer 怎么不撞、整机流程怎么不乱」的实打实解法。用对了加一只手臂只注册中介者老手臂一行不改用错了要么模块间网状耦合改不动要么中介者变成上帝对象比总控还臃肿。