新闻详情

新闻详情

首页 / 资讯中心 / 详情

简单工厂模式在设备软件里:硬件装配为什么不用一堆子类,而用一个工厂函数

发布时间:2026/9/4 22:38:25来源:尧图网络
简单工厂模式在设备软件里:硬件装配为什么不用一堆子类,而用一个工厂函数
这篇解决一个问题设备软件里几十上百个气缸、电机、传感器怎么创建才不写成子类爆炸也不写成裸 new 满天飞。一、痛点设备软件里的「对象创建」是个大麻烦一台半导体设备里硬件对象的数量远超一般软件气缸顶升、抱盘、解锁、安全门、压紧、导向……少则十几个多则几十个。电机轴X、Y、Z、变距、旋转……一台设备十几根轴很常见。传感器到位、原点、真空、叠料、安全触边……几十个起步。吸嘴一只手臂上 4×4 或 8×8 阵列好几只手臂加起来上百个。这些对象不是「一个类 new 一个」那么简单。同一个气缸类根据装配位置不同挂的控制组件、检测组件、超时时间、报警码全不一样。同一个电机轴根据用途不同限位、回零方式、加减速曲线全不一样。于是新手最容易写成三种烂代码。烂写法一每种硬件一个子类class Cylinder { ... }; class TopCylinder : public Cylinder { ... }; class ClampCylinder : public Cylinder { ... }; class SafetyDoorCylinder : public Cylinder { ... }; class UnlockCylinder : public Cylinder { ... }; // ... 50 个子类问题子类爆炸。50 个气缸 50 个类每个类只差几个参数代码重复到令人发指。加一个新气缸就要新建一个文件改工厂注册改 UI 列表。烂写法二裸 new 散落在初始化代码里// InitObject.cpp 里几百行 auto* cy1 new Cylinder(); cy1-SetControlSensor(sen1); cy1-SetDetectSensor(sen2); cy1-SetTimeout(3000); cy1-SetAlarmCode(8001); cy1-SetName(顶升气缸); cy1-SetAxisId(1); cy1-SetMoveOriginPos(0); cy1-SetMoveWorkPos(50); // ... 又一个气缸又是十行 auto* cy2 new Cylinder(); cy2-SetControlSensor(sen3); // ... 重复 50 次问题初始化代码几百上千行全是重复的 setter 调用。改一个参数要在海量代码里找。没有「装配信息」的集中管理。新人看到这段代码直接劝退。烂写法三用配置文件驱动但配置和代码两层皮# cylinder.ini[Cy1] Name顶升气缸 ControlSensorsen1 DetectSensorsen2 Timeout3000// 代码里读 ini逐个字段 setauto* cy new Cylinder(); cy-SetName(ini.Get(Cy1, Name)); cy-SetControlSensor(GetSensor(ini.Get(Cy1, ControlSensor))); // ... 读十几个字段问题配置和代码脱节。配置文件里写sen1代码里要查sen1对应哪个传感器对象。字段一多映射代码比裸 new 还长。类型安全也没了——配置里写错一个传感器名字运行时才崩。二、设计思路用一个工厂函数集中创建用装配枚举描述差异简单工厂的核心是把「创建对象」这件事从业务代码里抽出来集中到一个函数用参数描述「创建哪种变体」。设备软件里这个「参数」不是简单的类型枚举而是装配信息这个气缸挂什么传感器、什么控制阀、什么超时、什么报警码。设计分三步。第一步定义装配枚举把「这个气缸在机器上扮演什么角色」用枚举描述enum class CyAssembly { // 顶升系列 TopLift, // 顶升 TopLiftSafe, // 顶升安全锁 // 抱盘系列 Catch, // 抱盘 CatchSafe, // 抱盘安全 // 门系列 DoorLock, // 门锁 DoorOpen, // 门开 // 导向系列 YGuide1, // Y 向导向 YGuide2 // ... 按机器实际清单扩充 };这个枚举是「机器上有哪些气缸角色」的清单。加一个新气缸加一个枚举值即可不用新建类。第二步定义工厂函数// 返回值是基类指针调用方不关心具体子类Cylinder* CreateCylinder(CyAssembly assembly, int id, const std::string name);工厂函数内部根据assembly决定挂什么组件、设什么参数Cylinder* CreateCylinder(CyAssembly assembly, int id, const std::string name) { auto* cy new Cylinder(id, name); switch (assembly) { case CyAssembly::TopLift: cy-AddCap(CapType::Ctrl, TopLiftCtrlValve); cy-AddCap(CapType::Detect, TopLiftArriveSen); cy-AddCap(CapType::Detect, TopLiftOriginSen); cy-SetTimeout(3000); cy-SetAlarmBase(8001); break; case CyAssembly::Catch: cy-AddCap(CapType::Ctrl, CatchValve); cy-AddCap(CapType::Detect, CatchCheckSen); cy-AddCap(CapType::Detect, CatchSafeSen); cy-SetTimeout(5000); cy-SetAlarmBase(8101); break; case CyAssembly::DoorLock: cy-AddCap(CapType::Ctrl, DoorLockValve); cy-AddCap(CapType::Detect, DoorLockSen); cy-SetTimeout(2000); cy-SetAlarmBase(8201); break; // ...其它装配分支 } return cy; }第三步初始化代码只调工厂// InitObject.cpp m_pTopLift CreateCylinder(CyAssembly::TopLift, 1, 顶升气缸); m_pCatch CreateCylinder(CyAssembly::Catch, 2, 抱盘气缸); m_pDoor CreateCylinder(CyAssembly::DoorLock, 3, 门锁); // ...一行一个清晰干净对比之前裸 new 的几百行现在每加一个气缸只加一行。装配信息集中在工厂函数的switch里改参数只改一处。三、为什么不用子类继承这是新手最直觉的思路每种气缸一个子类。但设备软件里这条路走不通原因有三。原因一差异在「数据」不在「行为」顶升气缸和抱盘气缸的「行为」完全一样MoveTo(原点位)、MoveTo(工作位)、WaitArrive()、CheckSensor()。区别只在于挂哪个传感器、哪个阀、超时多久、报警码多少。这些全是配置数据不是行为差异。用子类继承来区分数据差异是大炮打蚊子。原因二子类数量随硬件数量线性增长一台设备 50 个气缸50 个子类。换一台设备气缸组合变了又是 50 个新子类。子类不跨设备复用等于每台设备从头写一遍。工厂 装配枚举换设备只改switch里的映射枚举和类本身不变。原因三子类无法运行时切换装配有时候同一个气缸在不同模式下要挂不同传感器比如 Clear 模式忽略真空检测。子类是编译期定的改不了。工厂函数可以加参数动态决定挂什么。四、进阶工厂 组件化Capability前面工厂函数里用了AddCap这是组件化思路。设备软件里硬件对象的差异很多时候是「能力」的差异有的气缸有原点传感器有的没有。有的气缸有安全触边有的没有。有的电机是伺服有的是步进。有的吸嘴有破真空检测有的只有真空检测。把这些「能力」拆成可插拔的组件比用继承更灵活。Capability 设计enum class CapType { Ctrl, // 控制阀 DetectArrive, // 到位检测 DetectOrigin, // 原点检测 DetectSafe, // 安全检测 DetectOverIC, // 叠料检测 }; struct Capability { CapType type; std::string sensorName; SensorObj* sensor nullptr; // SetHardWare 阶段完成绑定 };对象里用 Cap 池管理class Obj { protected: std::vectorCapability m_capPool; public: Capability* AddCap(CapType type, const std::string sensorName) { m_capPool.push_back({ type, sensorName, nullptr }); return m_capPool.back(); } Capability* GetCap(CapType type) { for (auto cap : m_capPool) { if (cap.type type) { return cap; } } return nullptr; } };工厂里挂 CapSetHardWare 里绑定// 工厂只描述对象具备哪些能力不绑定真实硬件指针 Cylinder* CreateCylinder(CyAssembly assembly, int id, const std::string name) { auto* cy new Cylinder(id, name); switch (assembly) { case CyAssembly::TopLift: cy-AddCap(CapType::Ctrl, TopLiftCtrlValve); cy-AddCap(CapType::DetectArrive, TopLiftArriveSen); cy-AddCap(CapType::DetectOrigin, TopLiftOriginSen); break; case CyAssembly::Catch: cy-AddCap(CapType::Ctrl, CatchValve); cy-AddCap(CapType::DetectArrive, CatchCheckSen); cy-AddCap(CapType::DetectSafe, CatchSafeSen); // 额外带安全检测 break; } return cy; }// SetHardWare把名字绑成真实传感器指针bool Cylinder::SetHardWare() { for (auto cap : m_capPool) { cap.sensor SensorObj::FindByName(cap.sensorName); if (!cap.sensor) { return false; } } return true; }为什么这样比继承好维度继承工厂 Capability加新组合新建子类加一个枚举 switch 分支能力差异多层继承或多基类AddCap/GetCap 可插拔运行时查能力dynamic_castGetCap 返回 nullptr 即没有跨设备复用子类绑死一台设备枚举 Cap 可重组一个气缸有没有安全传感器是GetCap(CapType::DetectSafe) ! nullptr的事不是dynamic_castSafeCylinder*的事。前者是数据查询后者是类型转换明显前者更轻。五、工厂函数的代码组织设备软件里工厂函数会越写越长因为硬件种类多。组织方式有讲究。方式一按硬件类型分文件CreateCylinder.cpp // 气缸工厂CreateAxis.cpp // 轴工厂CreatePicker.cpp // 吸嘴工厂CreateSensor.cpp // 传感器工厂每个文件一个工厂函数互不干扰。方式二按机器区域分函数一台设备分上料区、测试区、下料区工厂也按区分void CreateCylinders_LoadArea(); void CreateCylinders_TestArea(); void CreateCylinders_UnloadArea();每个区域函数内部调CreateCylinder。好处是区域独立换型时只改对应区域。方式三装配表驱动枚举值多了之后switch 会很长。可以用表替代struct CyAssemblyInfo { CyAssembly assembly; const char* ctrlName; const char* arriveName; const char* originName; const char* safeName; int timeout; int alarmBase; }; static const CyAssemblyInfo g_cyTable[] { {CyAssembly::TopLift, TopLiftCtrl, TopLiftArrive, TopLiftOrigin, nullptr, 3000, 8001}, {CyAssembly::Catch, CatchValve, CatchCheck, nullptr, CatchSafe, 5000, 8101} }; Cylinder* CreateCylinder(CyAssembly assembly, int id, const std::string name) { auto* info FindAssemblyInfo(assembly); auto* cy new Cylinder(id, name); cy-AddCap(CapType::Ctrl, info-ctrlName); if (info-arriveName) cy-AddCap(CapType::DetectArrive, info-arriveName); if (info-originName) cy-AddCap(CapType::DetectOrigin, info-originName); if (info-safeName) cy-AddCap(CapType::DetectSafe, info-safeName); cy-SetTimeout(info-timeout); cy-SetAlarmBase(info-alarmBase); return cy; }表驱动的好处加一行就是加一个气缸不用改 switch 逻辑。坏处是字符串名字失去编译期检查打错一个字母运行时才发现。实际项目里switch 和表驱动都行看团队偏好。switch 更适合枚举少、分支逻辑差异大的场景表驱动适合枚举多、结构统一的场景。接着上面被截断的地方继续。坑 3装配枚举和实际硬件脱节枚举里写了TopLift但机器上实际没接这个气缸。工厂函数里switch走到TopLift分支挂了一堆传感器名字结果SetHardWare时FindByName找不到返回失败。对策工厂函数里对「可选装配」做保护。或者初始化时先校验枚举和硬件清单的对应关系不匹配直接报错别等到SetHardWare才崩。坑 4工厂函数成了「上帝函数」一个CreateCylinder函数 500 行50 个case每个case里 10 行配置。改一个气缸要在 500 行里找。对策按区域拆子函数或用前面说的表驱动。别让一个函数长到没人敢动。坑 5忘记组件化又退回继承写着写着觉得「这个气缸特殊加个子类吧」。一个加两个加最后又回到子类爆炸。对策坚持「差异用 Cap 表达不用子类」。如果发现某个气缸真的行为不同不是数据不同才考虑子类。但 90% 的情况是数据差异不是行为差异。六、边界什么时候不该用简单工厂不该用 1对象创建逻辑极其复杂如果一个对象的创建需要读配置 → 连接硬件 → 校准 → 自检 → 注册回调这已经不是一个工厂函数能装下的。这时用建造者模式或初始化流程对象更合适。简单工厂适合「new 设几个参数」级别的创建。复杂创建流程硬塞进工厂会让函数变成几百行的怪物。不该用 2需要返回不同基类的对象简单工厂返回同一个基类指针。如果创建逻辑要返回完全不相关的类型说明抽象不对不是工厂能解决的。不该用 3工厂函数被到处调工厂函数应该在初始化阶段集中调用不在运行时业务代码里到处Create。如果运行时频繁创建销毁说明应该用对象池不是工厂。七、坑工厂模式常踩的坑 1工厂函数返回裸指针没人管析构Cylinder* CreateCylinder(...) { return new Cylinder(...); }// 调用方忘了 delete → 内存泄漏设备软件里硬件对象通常是全生命周期存活程序退出才销毁所以裸指针问题不大。但如果用了unique_ptr返回更安全unique_ptrCylinder CreateCylinder(...) { return make_uniqueCylinder(...); }坑 2工厂里做了太多业务逻辑// 错误工厂里调 MoveTo、CheckSensorCylinder* CreateCylinder(...) { auto* cy new Cylinder(...); cy-MoveTo(工作位); // 工厂里不该动硬件 cy-CheckSensor(); return cy; }工厂只负责「创建 配置」不负责「使用」。硬件动作放在SetHardWare之后的业务流程里。坑 3装配枚举和实际硬件脱节枚举接着上面被截断的地方继续。坑 3装配枚举和实际硬件脱节枚举里写了TopLift但机器上实际没接这个气缸。工厂函数里switch走到TopLift分支挂了一堆传感器名字结果SetHardWare时FindByName找不到返回失败。对策工厂函数里对「可选装配」做保护。或者初始化时先校验枚举和硬件清单的对应关系不匹配直接报错别等到SetHardWare才崩。坑 4工厂函数成了「上帝函数」一个CreateCylinder函数 500 行50 个case每个case里 10 行配置。改一个气缸要在 500 行里找。对策按区域拆子函数或用前面说的表驱动。别让一个函数长到没人敢动。坑 5忘记组件化又退回继承写着写着觉得「这个气缸特殊加个子类吧」。一个加两个加最后又回到子类爆炸。对策坚持「差异用 Cap 表达不用子类」。如果发现某个气缸真的行为不同不是数据不同才考虑子类。但 90% 的情况是数据差异不是行为差异。八、简单工厂 vs 抽象工厂 vs 工厂方法设备软件里这三个容易混简单说清楚。模式核心设备软件里的场景简单工厂一个函数参数决定创建哪种最常用CreateCylinder(枚举)工厂方法基类定义虚接口子类决定创建哪种少用除非同一种硬件有多个「家族」抽象工厂一个工厂创建一族相关对象少用除非要同时创建一整套配套硬件设备软件 90% 的场景用简单工厂就够。不要为了「显得高级」上抽象工厂那会让代码复杂度翻倍却没有实际收益。九、一个完整的实战例子把前面的点串起来看一个气缸从创建到使用的完整流程。定义// 枚举 enum class CyAssembly { TopLift, Catch, DoorLock, YGuide }; // 工厂 Cylinder* CreateCylinder(CyAssembly assembly, int id, const string name) { auto* cy new Cylinder(id, name); switch (assembly) { case CyAssembly::TopLift: cy-AddCap(CapType::Ctrl, TopLiftValve); cy-AddCap(CapType::DetectArrive, TopLiftArriveSen); cy-SetTimeout(3000); break; case CyAssembly::Catch: cy-AddCap(CapType::Ctrl, CatchValve); cy-AddCap(CapType::DetectArrive, CatchCheckSen); cy-AddCap(CapType::DetectSafe, CatchSafeSen); cy-SetTimeout(5000); break; } return cy; }装配// 初始化阶段 m_pTopLift CreateCylinder(CyAssembly::TopLift, 1, 顶升气缸); m_pCatch CreateCylinder(CyAssembly::Catch, 2, 抱盘气缸); // 统一 SetHardWare绑定传感器指针 m_pTopLift-SetHardWare(); m_pCatch-SetHardWare();使用// 业务代码里只管用不关心怎么创建的 m_pTopLift-MoveTo(工作位); if (!m_pTopLift-WaitArrive()) Alarm(顶升超时); // 查能力抱盘有没有安全传感器 auto* safeCap m_pCatch-GetCap(CapType::DetectSafe); if (safeCap safeCap-sensor) { if (!safeCap-sensor-ReadBit()) Alarm(抱盘安全未触发); }整个流程工厂描述拓扑 → SetHardWare 绑定指针 → 业务只管用。三层职责清晰加新气缸只动工厂。十、可复用结论设备软件里简单工厂最实用一个函数 装配枚举解决「同类型、不同配置」的对象创建。差异在数据用工厂差异在行为才用继承气缸、轴、吸嘴 90% 是数据差异。工厂 Capability 组件化能力可插拔比子类继承更灵活查能力用GetCap不用dynamic_cast。拓扑写工厂参数走配置硬件接线关系编译期确定数值参数运行期可调。工厂只在初始化调运行时频繁创建销毁用对象池不是工厂。别让工厂变上帝函数按区域拆或用表驱动保持可维护性。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

WezTerm 配置 10 分钟上手:换主题、调渐变背景、搭多窗格布局 2026/9/4 23:29:47

WezTerm 配置 10 分钟上手:换主题、调渐变背景、搭多窗格布局

WezTerm 配置 10 分钟上手:换主题、调渐变背景、搭多窗格布局 【免费下载链接】wezterm A GPU-accelerated cross-platform terminal emulator and multiplexer written by wez and implemented in Rust 项目地址: https://gitcode.com/GitHub_Trending/we/wezter…

阅读更多 →
Ice 菜单栏管理上手:5 个问题把 Mac 菜单栏整理干净(含刘海屏适配) 2026/9/4 23:29:47

Ice 菜单栏管理上手:5 个问题把 Mac 菜单栏整理干净(含刘海屏适配)

Ice 菜单栏管理上手:5 个问题把 Mac 菜单栏整理干净(含刘海屏适配) 【免费下载链接】Ice Powerful menu bar manager for macOS 项目地址: https://gitcode.com/GitHub_Trending/ice/Ice Ice 是一款 macOS 开源菜单栏管理工具&#xf…

阅读更多 →
FreeCAD Python API实战指南:5个脚本套路让模型从草图一路跑到交付 2026/9/4 23:29:47

FreeCAD Python API实战指南:5个脚本套路让模型从草图一路跑到交付

FreeCAD Python API实战指南:5个脚本套路让模型从草图一路跑到交付 【免费下载链接】FreeCAD Official source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler. 项目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD 改…

阅读更多 →
Claude HUD 完整指南:用一行状态栏看清上下文、工具与代理全貌 2026/9/4 23:29:47

Claude HUD 完整指南:用一行状态栏看清上下文、工具与代理全貌

Claude HUD 完整指南:用一行状态栏看清上下文、工具与代理全貌 【免费下载链接】claude-hud A Claude Code plugin that shows whats happening - context usage, active tools, running agents, and todo progress 项目地址: https://gitcode.com/GitHub_Trendin…

阅读更多 →
Koodo Reader:12种格式电子书阅读+云同步+AI助手,上手只需3步 2026/9/4 23:29:47

Koodo Reader:12种格式电子书阅读+云同步+AI助手,上手只需3步

Koodo Reader:12种格式电子书阅读云同步AI助手,上手只需3步 【免费下载链接】koodo-reader A modern ebook manager and reader with sync and backup capacities for Windows, macOS, Linux, Android, iOS and Web 项目地址: https://gitcode.com/Git…

阅读更多 →
如何把内部审批流从5天压到2小时:Budibase 运营自动化实战 2026/9/4 23:26:46

如何把内部审批流从5天压到2小时:Budibase 运营自动化实战

如何把内部审批流从5天压到2小时:Budibase 运营自动化实战 【免费下载链接】budibase AI agents, automations and apps that run your operations. Model agnostic. 项目地址: https://gitcode.com/GitHub_Trending/bu/budibase 上周三,某电商运…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞