本文首发于 http://www.YoungZY.com/
原文网址:Singleton Pattern
意图
- 确保一个类只存在一个实例,并供全局访问
- 适时初始化,或者在第一次使用时初始化
问题
应用程序需要一个,并且只需要一个对象的实例。另外,延迟初始化和全局访问是必要的。
讨论
单例的职责有:创建、初始化,访问和执行。声明一个私有的、静态的成员变量;提供一个公共的、静态的方法来封装初始化代码,并访问该实例。
客户端可以随时调用这个静态方法来获取单例。
只有在同时满足以下三个条件时才考虑使用单例模式:
- 单例的所有权不能被合理地指定
- 延迟初始化是值得的
- 除此之外没有别的全局访问点
如果单例的所属、初始化的时间和方式以及全局访问都不是问题,那么单例模式就没那么大吸引力了。
单例模式可以被扩展为支持应用程序指定数量的实例。
“静态的成员方法”这种方式不支持子类化。
删除单例是一个重要的设计问题。详见《消灭单例》。
结构
单例的类是可以直接访问的,并且在第一次使用时初始化。单例是私有的、静态的属性。获取单例的函数是公共的、静态的。
举例
美国的总统办公室就是一个单例。美国的宪法规定了总统的选举、任期和继任的章程。任何时间只会有一个在职总统。不管现任总统的个人身份是什么,在全球范围内“美国总统”指代的就是坐在那个办公室里的人。
核查清单(也可以理解为应用步骤)
- 在实现单例的类里定义一个私有的、静态的属性
- 相同的类里,定义一个公共的、静态的方法访问这个属性
- 在这个静态方法中实现“延迟初始化”(在第一次使用时初始化)
- 将该类的构造函数定义成
protected
或private
- 客户端仅通过第2步中的公共方法使用单例
经验法则
- 门面模式通常是单例的,因为只需要一个“门面”
- 状态模式通常是单例的
- 作为全局变量的“单例”的优势是你能精确掌握实例的数量,如果你想的话,可以管理任意数量的实例
- 单例模式是被误用最多的模式。当一个类有且仅有一个实例——不多也不少——时才应该使用单例。设计师经常错误地用单例去代替全局变量。总而言之,单例是一个全局变量。单例没有抛弃“全局”的概念,仅仅是另一种表达。
- 什么时候单例是不必要的?简单来说,是大部分时候。具体来说就是:当传递一个对象引用比把它设为全局变量更简单的时候。单例模式的真正问题在于它给了你一个好的借口——不用仔细地考虑这个对象合适的可见性。找到合理的“暴露-保护”的平衡是维持灵活性的关键。
- 我们团队在使用全局变量时有一个坏习惯,所以我建了一个“单例”的研究小组。接着我就发现到处都是单例模式,而全局变量引起的问题并没有解决。解决全局变量问题的方法不是把它变成单例,而是要问问自己到底为什么要使用全局变量。改个名字并不能解决问题。事实上,可能更糟,因为你会说“我没用全局变量,而是用了单例”,尽管这两个是一回事。
(译者注:详细的代码实例请移步 GitHub )
加入讨论