发布时间:2026/8/12 18:00:36
单例模式:在设备软件里总控、运动卡、数据库连接,怎么用才不烂 这篇解决一个问题设备软件里哪些东西该用单例哪些不该用以及为什么「随手 new 一个」和「全局 static」都不够好。一、痛点设备软件里的「全局唯一」到处都是写设备软件你一定会遇到这些东西运动控制卡全机只有一张所有轴都挂在它上面。总控对象整机状态机、停机入口、批次管理只有一个。数据库连接报警库、历史库一个连接够用。日志器全局一个所有模块都往里写。配置管理器开机读一次全机共享。这些东西天然「只有一个」。于是新手最容易写成两种烂代码// 全局变量谁都能改 MotionCard* g_pCard; CControl* g_pCtrl;问题命名冲突、无访问控制、初始化顺序不可控、没法保证别人不delete它。烂写法二随手 new到处传// 每个模块构造函数都收一堆指针 Arm(MotionCard* card, CControl* ctrl, Logger* log, Config* cfg, ...))问题参数列表爆炸、依赖关系难理、加一个全局资源要改所有构造函数。单例模式就是解决「全局唯一 受控访问」的。但设备行业用单例有几个坑下面逐个讲。二、设计思路单例解决什么单例的本质是保证一个类只有一个实例并提供全局访问点。它同时解决三件事唯一性不会有人new出第二个运动卡对象。全局访问任何模块都能拿到不用层层传参。受控初始化第一次访问时才创建初始化时机明确。设备软件里这三点正好对应前面说的运动卡、总控、日志器等场景。三、设备软件里适合单例的三类对象第一类硬件资源单例——运动控制卡全机一张卡所有轴、所有 IO 都通过它。不可能有两个实例。class MotionCard { public: static MotionCard Instance() { static MotionCard inst; // C11 局部 static线程安全 return inst; } // 初始化、运动、IO 操作 bool Init(int cardId); int MoveTo(int axis, double pos); int ReadInput(int bit); int WriteOutput(int bit, bool on); MotionCard(const MotionCard) delete; MotionCard operator(const MotionCard) delete; private: MotionCard() default; };为什么单例硬件只有一份两个对象操作同一张卡会冲突。关键点static MotionCard inst是 C11 起的线程安全局部静态变量不用自己加锁初始化。delete拷贝构造和赋值防止有人复制出第二个。构造函数private外部只能走Instance()。第二类系统级管理单例——总控对象总控管整机状态、停机入口、批次生命周期。全机一个。class MachineController { public: static MachineController Instance() { static MachineController inst; return inst; } void Run(); void Stop(); void Home(); void EndLot(); MachineState GetState() const; private: MachineController() default; MachineState m_state MachineState::Idle; };为什么单例整机状态只有一个真相来源任意模块都能触发停机。和「裸全局指针」的区别// 裸指针谁都能改可能空可能被 delete MachineController* g_pCtrl; // 单例受控访问不可能空不会被 delete MachineController::Instance().Stop();第三类共享服务单例——日志器、数据库、配置class Logger { public: static Logger Instance() { static Logger inst; return inst; } void Log(const string msg, Level lvl Level::Info); void SetLevel(Level lvl); private: Logger() default; };class AlarmDB { public: static AlarmDB Instance() { static AlarmDB inst; return inst; } bool Connect(const string connStr); bool WriteAlarm(int code, const string time, const string desc); private: AlarmDB() default; };为什么单例所有模块共享一个日志器/数据库连接避免重复创建、连接数失控。四、关键代码片段标准单例写法C11 起class Singleton { public: static Singleton Instance() { static Singleton inst; // 线程安全首次访问时初始化 return inst; } Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; private: Singleton() default; };这是 Meyers SingletonC11 保证局部 static 变量初始化是线程安全的。不需要double-checked locking不需要mutex。设备软件里常见的「带初始化参数」单例运动卡需要卡号数据库需要连接串。标准单例构造无参怎么传参数class MotionCard { public: static MotionCard Instance() { static MotionCard inst; return inst; } // 单独的初始化方法开机调一次 bool Init(int cardId, const string configPath) { if (m_bInited) return true; // 防重复初始化 // ... 打开卡、加载配置 m_bInited true; return true; } private: MotionCard() default; bool m_bInited false; };// 开机流程 MotionCard::Instance().Init(0, config/motion.ini); // 任意模块用 MotionCard::Instance().MoveTo(axis, pos);关键点构造函数无参初始化参数走Init方法。Init加防重复保护。五、边界什么时候不该用单例单例不是万能的设备软件里有几类东西不该做成单例。不该用 1每个工位一个的对象// 错误把 Buffer 做成单例 class Buffer : public SingletonBuffer { ... };Buffer 有 6 个、手臂有 5 只、料道有 4 条。这些是「多实例」对象用注册表管理不用单例。不该用 2有状态的业务对象// 错误把「当前批次」做成单例// 错误把「当前批次」做成单例 class CurrentLot : public SingletonCurrentLot { string lotId; int count; };批次会切换、会结束。单例的生命周期是整个程序批次的生命周期是一批。用普通对象 总控管理不用单例。不该用 3测试时需要替换的依赖单例的全局静态特性让单元测试很难 mock。如果一个对象在测试里要替换成假实现别用单例用依赖注入。// 测试友好接口 注入// 测试友好接口 注入 class Arm { IMotionCard* m_card; // 可以注入真卡或假卡 }; // 测试不友好单例写死 class Arm { void Move() { MotionCard::Instance().MoveTo(...); } // 测试时没法换 };六、坑设备软件里单例常踩的坑 1初始化顺序依赖// 日志器单例依赖配置单例 Logger::Logger() { auto path Config::Instance().Get(logPath); }如果Logger的Instance()先被调用触发Logger构造里面又调Config::Instance()。多数情况没问题C11 局部 static 按需初始化但如果有循环依赖就死锁。对策单例之间不要在构造函数里互相依赖。初始化参数走显式Init开机流程里按顺序调。坑 2单例里持有其它对象指针class MachineController : public SingletonMachineController { vectorActor* m_actors; // 持有所有 Actor };单例生命周期是整个程序m_actors里的对象如果比单例先析构单例析构时访问就崩。对策单例析构时不访问持有的对象只做清理标记。或者用shared_ptr管理生命周期。坑 3多线程访问可变状态class MachineController : public SingletonMachineController { MachineState m_state; // 可变状态 };多线程读写m_state要加锁或用atomic。单例的「唯一」不等于「线程安全」。对策单例的可变状态加mutex或atomic保护只读状态可以裸访问。坑 4把单例当全局变量用// 错误单例里塞一堆业务状态 class God : public SingletonGod { int currentStep; bool armBusy; double axisPos; // ... 50 个字段 };这就是披着单例外衣的全局变量封装等于没有。对策单例只放「真正全局唯一」的资源。业务状态归各自 Actor不要往单例里塞。七、单例 vs 依赖注入设备软件怎么选这是设备架构里常纠结的点。两种都能解决「全局访问」取舍看场景。维度单例依赖注入访问方式X::Instance()全局可达构造函数传指针耦合隐式谁都能调显式参数列表测试难 mock易 mock适合硬件、基础设施业务模块例子运动卡、日志、DB手臂、策略、工位设备软件的实践通常是混合基础设施层用单例运动卡、日志、数据库、配置。业务层用依赖注入手臂收策略指针、策略收手臂指针。不要强求统一。基础设施用单例省得层层传参业务用注入保持可测可替换。八、可复用结论单例适合三类硬件资源运动卡、系统管理总控、共享服务日志/DB/配置。标准写法C11 局部 static delete 拷贝 private 构造不用手写锁。初始化参数走Init方法构造函数保持无参避免循环依赖。不该用单例多实例对象、有状态业务对象、需要测试替换的依赖。单例不等于线程安全可变状态仍要加锁。基础设施用单例、业务用依赖注入混合是设备软件的务实选择。单例在设备软件里不是「设计模式的练习题」而是解决「全局唯一硬件资源怎么管」的实打实手段。用对了省掉一大半参数传递用错了就是披着模式外衣的上帝对象。

相关新闻

2026/8/12 18:00:36

android-实例-蒲公英-更新与安装-4-安装(问题解决)

android-实例-蒲公英-更新与安装-3-我的应用-CSDN博客 通过app在线,点击在线更新蒲公英网站自传app版本更新 安装APK函数installApk(File apkFile) // 6. 安装 APK private void installApk(File apkFile) {Intent intent new Intent(Intent.ACTION_VIEW);Uri …

2026/8/12 18:00:36

Unity3D集成阿里小云KWS:实现游戏本地语音唤醒与实时交互

1. 项目概述:当游戏“听懂”你的声音 在游戏开发领域,沉浸感是永恒的追求。从手柄的震动反馈到VR的视觉冲击,我们一直在寻找让玩家“身临其境”的下一块拼图。而语音交互,这个在智能家居和手机助手中已司空见惯的技术,…

2026/8/12 18:00:36

谁说非程序员不能做软件?我用XDevelop搓出了一个带AI的管理系统

引言:打破“编程”的认知壁垒长久以来,“软件开发”似乎被贴上了“程序员专属”的标签,仿佛只有精通多门编程语言、深谙算法与架构的工程师才能创造软件。这种观念将无数有想法、有需求的人挡在了门外。但今天,我想用我的亲身经历…

2026/8/12 19:05:42

告别Wi-Fi连接烦恼:Realtek 8852AE驱动安装全攻略

告别Wi-Fi连接烦恼:Realtek 8852AE驱动安装全攻略 【免费下载链接】rtw89 Driver for Realtek 8852AE, an 802.11ax device 项目地址: https://gitcode.com/gh_mirrors/rt/rtw89 还在为Linux系统下的Wi-Fi连接问题而烦恼吗?如果你的笔记本电脑使用…

2026/8/12 19:05:42

LocalVocal:构建企业级私有化语音识别与实时翻译解决方案

LocalVocal:构建企业级私有化语音识别与实时翻译解决方案 【免费下载链接】obs-localvocal OBS plugin for local speech recognition and captioning using AI 项目地址: https://gitcode.com/gh_mirrors/ob/obs-localvocal 在当今数字化协作环境中&#xf…

2026/8/12 19:05:42

现代Web表格开发:从基础架构到性能优化的实战指南

1. 项目概述:从“表格”到“数据界面”的认知跃迁“Web课程table相关学习笔记”——这个标题看起来平平无奇,甚至有些学生气。但作为一名和前端打了十几年交道的开发者,我深知这个看似基础的“表格”,恰恰是Web开发中一个深不见底…

2026/8/12 19:05:42

PP视频下载安装教程(免费影视体育直播)

一、先看:PP视频到底能看什么? 想看免费电影、追剧追番、又不想开视频平台会员?或者你是足球迷,想免费看德甲、意甲、中超这些顶级联赛直播?PP视频(前身 PPTV 聚力 / PPLive)就是为这两类需求而…

2026/8/12 10:37:12

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/12 5:35:25

当 LLM 遇见大文档:主流开源项目如何处理上下文超限

从 Agentic Loop 到 Repo Map,七种策略与六类陷阱引言:128K vs 10MB 的硬冲突 2026 年的 LLM 上下文窗口已达到 128K ~ 1M token(≈ 0.5MB ~ 4MB 文本),但 LLM 想要处理的真实数据规模远远超过这个量级:真实…

2026/8/12 9:34:08

Ubuntu 23.10中双击运行.sh文件的完整指南:从权限原理到桌面配置

1. 项目概述:从一次“双击”引发的权限探索在Ubuntu桌面环境下,我们习惯了双击运行那些带有.exe后缀的Windows程序安装包,但当你拿到一个以.sh结尾的Shell脚本文件时,满怀期待地双击它,却很可能只看到一个文本编辑器窗…

2026/8/12 9:34:08

NumPy条件索引实战:np.where与np.argwhere高效数据筛选指南

1. 从一次数据筛选的“笨办法”说起 前几天,我帮一个刚入行的数据分析师同事看代码,他正在处理一批传感器数据,需要找出所有温度超过阈值的数据点,然后进行后续分析。我一看他的实现,好家伙,一个 for 循环…

2026/8/12 9:34:08

基于Docker与Selenium Grid构建高可用浏览器自动化测试环境

1. 项目概述:为什么需要容器化的浏览器自动化?在软件开发和测试领域,浏览器自动化早已不是新鲜事。无论是日常的UI回归测试、数据抓取,还是复杂的业务流程模拟,Selenium都是我们绕不开的利器。然而,但凡在团…

2026/8/10 11:20:30

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/11 17:06:59

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/11 3:05:11

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…