前言
近期逐渐意识到要更进一步提升能力,要提高自己的姿势水平,还是得多学习一个,要非常熟悉Design pattern这一套,毕竟还是too young。(再也不能+1s了)
虽然有可能我在不了解Design pattern的情况下已经在使用了,但是和人交流的时候使用共通的专业用语可以大幅度提高沟通效率。比如在交流时说“噢,对于那个问题,只需使用单例(Singleton)就好”,每个人都会理解你建议的背后思想。如果大家都了解这个模式及其名称,就无需解释单例是什么。不然还要巴拉巴拉一通解释一通说,还不一定说得清楚。因此开始每几天学一个,用一个Design Pattern。
这是这套的第一篇,希望我能坚持下来吧。学习教材使用的是Refactoring.Guru,为了提高效率会和ChatGPT并用,大部分时间ChatGPT的解释是非常正确且详细的,所以我会在进行查阅和编辑后贴上,同时自己会再加上一些自己的理解和应用实例。如果有幸能在这段话里遇到你我还是推荐优先阅读原文Singleton,之后也可以看看本文的在Unity中Singleton的实例
,原文真的写的非常浅显易懂了,你信我啊!
目的
单例是一种创建型设计模式(creational design pattern
),它确保一个类只有一个实例,并提供对这个实例的全局访问点。
问题
单例模式同时解决了两个问题,但违反了单一职责原则(Single Responsibility Principle
):
- 确保一个类只有单个实例:控制类实例的数量的常见原因是为了控制对某些共享资源的访问,比如数据库或文件。
- 提供对该实例的全局访问:单例模式允许从程序的任何地方访问某个对象,同时保护该实例不被其他代码覆写。
解决方案
单例的所有实现通常有两个共同点:
- 将默认构造函数设为私有,防止使用
new
操作符与单例类。 - 创建一个静态的创建方法,充当构造函数。在底层,此方法调用私有构造函数创建对象,并在静态字段中保存。此后对此方法的所有调用都将返回缓存的对象。
现实世界的类比
政府是单例模式的绝佳示例。一个国家只能有一个官方政府。不管组成政府的个人身份如何,“X国政府"这个名称是一个全球性的访问点,指代负责的那群人。
结构
- 单例类声明了静态方法
getInstance
,返回其自身类的相同实例。 - 单例的构造函数应对客户端代码隐藏。调用
getInstance
方法应是获取单例对象的唯一方式。
伪代码
|
|
Singleton 模式的适用场景
1. 单例存在的需求
- 当程序中的某个类只应该有一个实例,且这个实例需要被程序的不同部分共享时,应当使用
Singleton(单例)
模式。例如,一个被程序不同部分共享的单一数据库对象。
2. 创建对象的限制
Singleton
模式禁用了除特殊创建方法外的所有其他创建类实例的手段。这个方法要么创建一个新对象,要么如果该对象已经被创建,则返回已存在的对象。
3. 对全局变量的控制
- 使用
Singleton
模式当你需要对全局变量有更严格的控制。
4. 单例与全局变量的区别
- 与全局变量不同,
Singleton
模式保证类只有一个实例。除了Singleton
类本身外,没有任何东西可以替换缓存的实例。
5. 单例实例数量的调整
- 注意,你总是可以调整这个限制,允许创建任意数量的
Singleton
实例。需要改变的唯一代码片段是getInstance
方法的主体。
重要点:
- 单例模式的核心在于确保一个类仅有一个实例,并提供一个全局访问点。
- 与全局变量相比,单例模式提供了对实例化更精确的控制,尤其是在多线程环境中。
- 在某些情况下,比如调整
getInstance
方法,可以灵活地改变单例模式的严格性,但这通常需要谨慎考虑。
如何实现
- 添加一个私有的静态字段,用于存储单例实例。
- 声明一个公共的静态创建方法,用于获取单例实例。
- 实现“懒加载”(
lazy initialization
),在静态方法内部在首次调用时创建新对象,并存放于静态字段中。该方法应始终在后续调用中返回该实例。 - 将类的构造函数设为私有。类的静态方法仍然可以调用构造函数,但其他对象不行。
- 修改客户端代码,用对静态创建方法的调用替换对单例构造函数的直接调用。
在Unity中Singleton的实例
创建一个Singleton的模版类,我所使用的Singleton模版类如下
|
|
然后创建所要使用的Singleton并且继承这个模版。我在CommonParameterContainer中使用了Singleton用来存储单一Scene中的通用Parameter。
|
|
此时就可以在其他的Script中通过CommonParameterContainer.Instance.xxx;
来调用Parameter,或者在script开头以commonParamCont = CommonParameterContainer.Instance;
方式获取该单例的实例。以前总是用全局变量或者通过反复GetComponent
的方式来调用Parameter,现在就优雅多了。
优点与缺点
-
确保类只有一个实例。
-
提供对该实例的全球性访问点。
-
单例对象仅在首次请求时初始化。
-
违反单一职责原则。模式同时解决了两个问题。
-
单例模式可能掩盖糟糕的设计,例如,程序组件过于相互了解。
-
在多线程环境中需要特殊处理,以避免多个线程多次创建单例对象。
-
单例的客户端代码可能难以单元测试。
与其他模式的关系
- 外观模式(
Facade
):通常可以转换为单例,因为大多数情况下单个外观对象就足够了。 - 享元模式(
Flyweight
):如果你以某种方式将所有共享状态的对象减少到一个享元对象,它就类似于单例。但两者有本质区别:单例只应有一个实例,而享元类可以有多个具有不同内在状态的实例;单例对象可以是可变的,享元对象是不可变的。 - 抽象工厂(
Abstract Factory
)、建造者(Builder
)、原型(Prototype
)都可以实现为单例。