外观模式:设备软件里「一键停机」「一键回零」背后的设计
发布时间:2026/8/25 12:16:38 作者:尧图编辑部 阅读量:1,286

这篇解决一个问题设备软件里有几十个模块总控要一键操作它们时怎么写才不在总控里堆几十行重复调用。从最直觉的写法开始一步步讲清外观模式在设备软件里解决什么。外观模式设备软件里「一键停机」「一键回零」背后的设计这篇解决一个问题设备软件里有几十个模块总控要一键操作它们时怎么写才不在总控里堆几十行重复调用。从最直觉的写法开始一步步讲清外观模式在设备软件里解决什么。一、先从一个生活例子说起你家里有电视、音响、投影仪、灯光、窗帘。看电影时要打开投影仪。放下投影幕。打开音响。调灯光到暗。关窗帘。每次看电影都要按五个遥控器各按一遍。累不累于是你买了一个「智能家居中控」上面有一个按钮叫「观影模式」。按一下中控自动帮你把这五件事都做了。你不用知道每个设备怎么开只管按一个按钮。这个「智能家居中控」就是外观。外观的本质把一堆复杂的子系统操作包成一个简单的入口。调用方只管按一个按钮不用知道背后调了几个设备、什么顺序。二、回到代码不用外观模式会怎样设备软件里最典型的场景停机。停机不是「设一个标志」就完了。一台设备停机要做停运动控制卡防止轴继续动。停所有 Actor 线程手臂、料道、Buffer、测试台……十几个。关所有门锁。关所有真空。改整机状态为暂停或停机。上报 SECS 暂停事件。刷新 UI 显示。最直觉的写法在总控里堆void CControl::Stop() { // 停卡 m_pDmc5812-Stop(); // 停所有 Actor m_pArmTray[0]-Stop(); m_pArmTray[1]-Stop(); m_pArmBIB[0]-Stop(); m_pArmBIB[1]-Stop(); m_pArmSortBin-Stop(); m_pChannel[0]-Stop(); m_pChannel[1]-Stop(); m_pBuffer[0]-Stop(); m_pBuffer[1]-Stop(); m_pBuffer[2]-Stop(); m_pTester-Stop(); m_pCatchArm-Stop(); m_pTrayTransfer-Stop(); // ... 还有很多 // 关门锁 for (int i 0; i m_DoorLock.size(); i) m_DoorLock[i]-WriteBit(false); // 关真空 m_pArmTray[0]-ArmPickerBreakAir(true); m_pArmTray[1]-ArmPickerBreakAir(true); // ... // 改状态 if (GetEndedLot()) SetMacState(PS_HALT); else SetMacState(PS_PAUSE); // 上报 SECS if (!UpdateEvent(SECS_EVENT::Pause, 机台暂停)) Alarm(SECS上报失败); // 刷 UI pDlg-UpdateMachineState(); }问题一堆问题一总控知道太多细节。CControl::Stop知道有哪几只手臂、几个 Buffer、几个门锁。加一个模块这里要加一行。删一个模块这里要删一行。总控和所有子模块耦合在一起。问题二重复调用散落。 停机要遍历所有 Actor回零也要遍历所有 Actor启动也要。三处各写一遍遍历加模块要改三处。问题三顺序写死在总控里。 先停卡再停 Actor这个顺序写在Stop里。如果回零要先回 Actor 再回卡又是另一个顺序。顺序逻辑散落在总控的各个方法里。问题四没法复用。 另一台设备也要停机但模块不一样。这段代码搬不过去因为写死了具体模块。根本问题总控直接面对所有子模块没有中间层。三、外观模式怎么解决思路在总控和子模块之间加一层「外观」把「停所有 Actor」「关所有门锁」「关所有真空」这些批量操作包成简单方法。总控只调外观外观内部遍历子模块。第一步注册表——外观的基础外观要遍历所有子模块首先得知道有哪些子模块。这就是注册表的作用class Actor { public: static vectorActor* g_Actors; // 所有 Actor 的注册表 static void RegistActor(Actor* a) { g_Actors.push_back(a); } };每个 Actor 创建时注册auto* arm new Arm(1, ArmTray0); Actor::RegistActor(arm); auto* channel new Channel(2, Channel0); Actor::RegistActor(channel); // ...第二步外观方法——批量操作在 Actor 基类里提供静态方法遍历注册表class Actor { public: static vectorActor* g_Actors; // 外观停所有 Actor static void StopAllActors() { for (auto* a : g_Actors) a-Stop(); } // 外观启动所有 Actor static void RunAllActors() { for (auto* a : g_Actors) if (!a-GetEndedLot()) a-Run(); } // 外观回零所有 Actor static void HomeAllActors() { for (auto* a : g_Actors) a-Home(); } // 外观结批所有 Actor static void EndLotAllActors() { for (auto* a : g_Actors) a-EndLot(); } // 外观创建所有线程 static void BeginAllThreads() { for (auto* a : g_Actors) a-CreateThread(); } };第三步总控只调外观void CControl::Stop() { m_pDmc5812-Stop(); // 停卡 Actor::StopAllActors(); // 停所有 Actor一行 CloseAllDoorLocks(); // 关所有门锁外观 CloseAllVacuum(); // 关所有真空外观 if (GetEndedLot()) SetMacState(PS_HALT); else SetMacState(PS_PAUSE); UpdateSECS(Pause, 机台暂停); pDlg-UpdateMachineState(); }对比之前几十行的Stop现在清爽了。总控不关心有哪几只手臂、几个 Buffer只管「停所有 Actor」。加新模块时// 加一个新模块 auto* newModule new SomeActor(99, NewModule); Actor::RegistActor(newModule); // Stop 不用改StopAllActors 自动包含新模块 // Run 不用改RunAllActors 自动包含 // Home 不用改HomeAllActors 自动包含这就是外观模式的价值加模块只注册不改任何批量操作代码。四、外观模式的标准结构角色例子Facade外观Actor::StopAllActors/RunAllActors/HomeAllActorsSubsystem子系统各个 Actor 子类Arm、Channel、Buffer、TesterClient调用方CControl只调外观方法ClientCControl │ 调 Facade.StopAllActors() ▼ FacadeActor 静态方法 │ 遍历注册表调每个子系统的 Stop() ▼ SubsystemArm / Channel / Buffer / ... 各自执行自己的 Stop()关键Client 不直接面对子系统的每个对象只面对外观的统一入口。外观内部负责遍历、顺序、错误处理。五、外观模式解决了什么问题不用外观用外观总控知道太多列出每个模块只调 StopAllActors加模块要改改总控每个方法只注册不改重复遍历Stop/Run/Home 各写一遍外观统一遍历顺序管理散落在总控各处外观内部统一复用总控和具体模块绑死外观可跨设备复用一句话外观模式把「对一堆子系统的批量操作」包成一个简单入口让调用方不用面对子系统的复杂性。六、设备软件里的真实场景场景一一键停机前面讲的Stop就是典型。总控调外观外观遍历所有 Actor 停掉。void CControl::Stop() { m_pDmc5812-Stop(); Actor::StopAllActors(); // 外观一行停所有 // ... }场景二一键启动启动时要创建所有线程 设置运行标志 配置路线 上报 SECS。void CControl::Run() { UpdateActorWorkMode(); // 配置路线 Actor::RunAllActors(); // 外观所有 Actor 设运行标志 Actor::BeginAllThreads(); // 外观所有 Actor 创建线程 UpdateSECS(Start, 机台启动); }没有外观的话这里要遍历g_Actors写两遍。场景三一键回零回零要所有模块各自回零还要等全部回零完成。void CControl::Home() { Actor::HomeAllActors(); // 外观所有 Actor 开始回零 // 等所有回零完成 while (!Actor::CheckAllThreadHomeOK()) Sleep(100); SetMacState(PS_IDLE); }场景四一键结批结批要所有模块按依赖顺序停止前面讲的ConnectEndLotAfter就是结批链而EndLotAllActors是外观入口。void CControl::EndLot() { Actor::EndLotAllActors(); // 外观所有 Actor 标记结批 std::thread(Actor::EndLotFlow).detach(); // 启动结批流程线程 }场景五硬件统一操作不只是 Actor硬件层也可以有外观。比如「关所有真空」「关所有门锁」「所有轴回安全位」class HardwareFacade { public: static void AllAxisToSafe() { for (auto* arm : ArmBase::g_AllArmBase) arm-ArmZToSafe(); } static void AllBreakAir() { for (auto* arm : ArmBase::g_AllArmBase) arm-ArmPickerBreakAir(true); } static void AllDoorLockClose() { for (auto* lock : g_DoorLocks) lock-WriteBit(false); } };急停时一行调三个外观方法不用在总控里写几十行遍历。七、外观模式 vs 注册表什么关系外观模式和注册表经常一起用但不是一回事。注册表外观模式作用记录「有哪些对象」提供「批量操作入口」数据一个列表一组方法关系外观依赖注册表来遍历注册表是外观的实现基础注册表是数据知道有谁外观是行为对它们批量操作。注册表让外观能遍历所有子模块外观让注册表不只是「存着没用」。八、外观模式的坑坑一外观变成上帝函数// 错误外观里塞业务逻辑 class Facade { public: static void StopAll() { for (auto* a : g_Actors) { a-Stop(); if (a-GetType() Arm) { ((Arm*)a)-ArmZToSafe(); // 外观里做类型判断 ((Arm*)a)-ArmPickerBreakAir(true); } else if (a-GetType() Channel) { ((Channel*)a)-CloseDoor(); // 外观里调具体方法 } } } };外观里做类型判断、调子类特有方法说明外观越权了。外观应该只调公共接口Stop不该知道子类细节。对策需要子类特有操作时在基类定义虚函数子类各自实现外观只调虚函数。class Actor { public: virtual void SafeStop() // 基类定义子类实现各自的安全停止 { Stop(); // 默认只 Stop } }; class Arm : public Actor { void SafeStop() override { Stop(); ArmZToSafe(); // 手臂额外抬 Z ArmPickerBreakAir(true); } }; class Channel : public Actor { void SafeStop() override { Stop(); CloseDoor(); // 料道额外关门 } }; // 外观只调虚函数 static void SafeStopAll() { for (auto* a : g_Actors) a-SafeStop(); // 多态各自做自己的安全停止 }坑二外观和子系统双向依赖外观知道子系统子系统又反过来调外观形成循环。对策子系统不调外观外观调子系统。单向依赖。坑三外观方法太多什么操作都往外观里塞外观有几十个方法比总控还臃肿。对策外观只放「高频批量操作」StopAll、RunAll、HomeAll。低频的、单个模块的操作直接调模块不走外观。坑四遍历时模块正在增删StopAllActors正在遍历g_Actors另一个线程往g_Actors加了一个新模块。遍历过程中列表被修改迭代器失效崩。对策注册表加锁或者初始化阶段注册完就锁死运行期不允许增删。九、外观模式 vs 中介者别搞混这两个都涉及「一个中间层管多个模块」容易混。外观模式中介者模式方向单向外观调子系统双向中介者调子系统子系统也调中介者目的简化调用入口解耦模块间通信子系统间互不知道互不直接通信通过中介者例子StopAllActors各模块通过中介者协调不直接互调简单记外观是「总机」——你打一个号它帮你转中介者是「调度员」——模块之间要沟通都找它不直接互调。十、可复用结论外观模式的本质把对一堆子系统的批量操作包成一个简单入口让调用方不面对子系统的复杂性。三个角色Facade批量操作入口、Subsystem各模块、Client总控。设备软件最典型应用一键停机、一键启动、一键回零、一键结批——全是外观。核心价值加模块只注册不改批量操作总控不列每个模块遍历逻辑集中不散落。外观只调公共接口不做类型判断、不调子类特有方法——需要特有操作用虚函数多态。和注册表的关系注册表是数据有谁外观是行为批量操作。外观依赖注册表遍历。和中介者的区别外观单向调子系统中介者双向协调子系统间通信。外观模式在设备软件里不是「设计模式课本上的 Facade」而是「总控怎么一键管几十个模块」的实打实手段。没有外观总控里堆满遍历和具体模块名有了外观总控一行StopAllActors搞定加模块只注册不改。这就是为什么所有成熟设备软件都有StopAll / RunAll / HomeAll这类静态方法——它们就是外观模式的工程化落地。