设计模式 - 单例模式 笔记

核心目的A:逻辑上的绝对唯一(领域规则)

  • 确保一个类在整个应用中只有 唯一实例,并提供一个全局统一访问入口。
  • GoF 设计模式的「定义本意」是:强制全局只能有一个,不允许第二个。

举例: 考虑一个【全局音频播放器】 的设计。在业务层面上,我们需要一个唯一的音频出口,避免多个出口造成音频播放混乱。这一刚性约束,要求播放器对象在逻辑上必须是唯一的。此时,单例模式的核心目标正是强制实现这种业务逻辑的唯一性 ;而它所附带的调用 方便资源节约等优点,在此场景下,并非首要设计动机。


核心目的B:资源上的收敛与复用(工程决策)

  • 在很多实际工程场景下,设计单例的第一驱动力就是方便和效率,而不是那个"绝对唯一"的哲学限制。

举例: 假设我们要设计一个【公司】的程序,公司里有一台【打印机】,各个员工对象都需要用它。但问题是,打印机的位置并不明确,每个员工都得花时间先"找到"它、记下来,然后才能用。这样一来,光是找打印机就折腾半天。于是,我们把【打印机】设计成单例,让它变成一个全局都知道、一下子就能找到的东西。员工不用再费劲去寻找和缓存,可以直接调用。这时候,设计单例的首要目的,就是全局共享、方便调用、提高效率。至于"唯一性",只是顺手实现的结果,并不是重点------因为将来公司完全可能添置第二台、第三台打印机,到时候再动态调整吧!

  • 全公司员工共用一台打印机对象
  • 每个人不用自己 new 一台打印机
  • 节省硬件 / 内存资源
  • 统一排队、统一控制打印任务👉 核心:复用、方便、提效率、统一调度

注意:

  • 单例的"霸道"之处: 它不仅提供方便,还强制剥夺了你拥有第二个实例的权利。

  • 后果: 如果公司业务扩大,买了第二台打印机。因为你当初用了"单例模式",你的代码里满屏都是 Printer.Instance.Print()。这时你会发现,原本为了"方便"设计的架构,成了限制公司扩展的最大阻碍------你得重构整个系统的调用逻辑才能支持两台打印机。

世界上唯一的真理 ------ 就是"唯一"是暂时的。

结合【代理模式】解决方案:

1. 实现"伪单例",保留"真扩展"

你可以设计一个 PrinterProxy 类。对全公司的员工(其他对象)来说,他们看到的依然是一个"单例"接口,但这个代理背后大有文章:

  • 初期(只有一台打印机): 代理类内部持有一个单例,员工调用 Proxy.Print(),代理就转交给那台唯一的打印机。

  • 后期(增加了第二台):不需要修改任何员工的代码 。你只需要修改 Proxy 内部的逻辑,让它根据负载均衡、部门权限或者打印机状态,去决定把任务分发给哪一个真实的打印机实例。

2. 这种做法的"高级"之处

借助代理模式,你实际上实现了一个"动态路由":

  1. 调用依然方便: 员工依然是 PrinterProxy.GetInstance().Print(),满足了你说的"方便调用"的第一目标。

  2. 隐藏复杂性: 员工不需要知道后台是有 1 台还是 100 台打印机,也不需要知道打印机是在本地还是在云端。

  3. 打破单例的"诅咒": 真正的 Printer 类本身不再是单例了,它可以被多次实例化。单例的限制被挪到了"代理"这一层,而代理是逻辑性的,非常容易重构。

注意:没有最好的设计,只有最契合当前业务阶段的设计。




单例模式 与 纯静态类 区别:

单例的本质,是一个活在实例化环境中的全局对象。 它享有普通对象的一切权利------可以被引用关联、实现接口、延迟加载、或被继承。甚至,你可以在运行时动态地卸载它,或替换成另一个兼容对象。它和系统中其他实例对象"平起平坐"。

而静态类或静态对象,则站在更高维度的全局空间中。 它游离于对象协作网络之外,不参与实例化的具体场景。正因如此,它最适合担当"无状态的纯工具角色"------即那些不依赖实例上下文、完全通用的逻辑。同时,它也是"终身且唯一"的,你无法像替换单例那样,在运行时卸载或切换它。




"唯一"不等于"必须用单例":

很多小伙伴在做层级制设计时,容易犯一个迷糊。比如说,我们要写一个代表公司的软件,里面有CEO、部门经理、底层员工。CEO 是全局唯一的,又处在层级制的最顶层------第一印象往往是:"它既然是唯一的,那就该是个单例吧?" 于是就这么走上了过度设计的不归路。

但 CEO 到底该不该设为单例,不应该凭"直觉上的唯一",而要回到具体的业务需求上来判断。

先问自己几个问题:

如果我们的"公司"本身只是一个功能模块,把 CEO 设计成单例就会出问题。让它全局化,会立刻破坏模块的封装性------单例变成了模块外也能随便拿到的东西,边界就模糊了。而且,如果只是为了"设计而设计",没有清晰的使用目的,那这个单例就更值得警惕:到底谁会用到它?是其他功能模块,还是底下的员工需要频繁和 CEO 打交道?这些问题没搞清楚之前,贸然给 CEO 套上单例,很可能就是在给未来挖坑。

单例真正的用武之地,往往不在层级制的顶层,而在底层那些共享设施上。

单例模式的核心价值,从来不是"这个东西很重要,所以只能有一个",而是:"这个资源需要被全局共享、统一管控,所以要收敛为一个实例。"

具体来说,这些角色才是单例的最佳人选:

软件中的单例 层级制中的类比 为什么用单例
数据库连接池 供水/供电部门 全局只有一个池子,避免资源耗尽
日志记录器 档案室/秘书处 所有部门写日志,应该写在同一个地方
配置管理器 规章制度手册 全公司共用一套配置,改一处处处生效
打印机接口 公共打印室 物理设备共享,请求必须排队通过

这些角色的共性是:无状态、共享性、资源性。它们服务于所有人,而不是指挥所有人。

一句话总结: 判断要不要用单例,不看对象在层级上有多高、多重要,而看它是不是一个需要被全局共享和统一管控的底层设施。CEO 再唯一,也未必轮到单例来登场。

相关推荐
cui_ruicheng1 小时前
Linux线程(四):线程池、日志系统与单例模式
linux·开发语言·单例模式
AOwhisky1 小时前
Docker 学习笔记:网络篇
linux·运维·网络·笔记·学习·docker·容器
24白菜头1 小时前
MySQL学习笔记
数据库·笔记·学习·mysql
雪度娃娃1 小时前
结构型设计模式——外观模式
c++·设计模式·外观模式
问心无愧05131 小时前
ctf show web入门54
前端·笔记
小陈phd1 小时前
多模态大模型学习笔记(三十九)——生成式与Transformer式OCR:从“像素抄录“到“文档智能“的完整演进
笔记·学习·transformer
泡泡以安1 小时前
Unidbg学习笔记(一):为什么需要用户态模拟器
笔记·学习
蜡笔小马1 小时前
05.C++设计模式-适配器模式
c++·设计模式·适配器模式
暖馒1 小时前
WPF绑定由简到繁深入笔记
笔记·wpf