意图
Singleton是创建模式的一种,让你可以确保一个类只有一个实例,并为此实例提供一个全局访问点。
问题
Singleton同时解决了两个问题(违反了单一职责原则):
确保一个类只有一个实例。最常见原因是控制一些共享资源,比如,数据库。
假设你已经创建了一个新对象,不久,又尝试创建一个新的。在这种秦光下,你想要老的那个对象而不是新创建一个实例。
它不能通过正常的构造方法完成,因为在设计上每个构造方法总是返回一个新对象。
为实例提供一个全局的访问点。听起来像一个全局变量,不是吗?但是你无法做一个只读的全局变量。任何可以访问这个变量的人都可以替换他的值。
还有另外一个问题:你不希望解决以前的问题的代码分散在你的程序中。它们最好放在一个类中,特别当你的代码已经依赖那个类时。
注意,Sigleton同时解决了上面两个问题。但是现在模式很流行,即使他只解决了其中一个问题人们也会把它称为Sigleton。
解决
单例所有实现的都有以下两个步骤:
创建私有的构造方法。
创建静态的创建方法扮演构造方法的角色。这个方法使用私有的构造方法创建一个对象并把它保存在静态变量或者字段中。对这个方法的所有调用都将返回缓存的对象。
Singleton保持把单实例的生产代码放在一个地方–Singlton类的创建方法中。任何可以访问Singleton类的客户端也都可以访问他的创建方法。因此,他提供给我们Singleton实例的一个全局访问点。
真实世界的类比
政府
政府是Singleton模式的一个很好的例子。一个国家只能有一个官方政府。不管组件政府的个人身份如何,“X的政府”这个称号是全球的一个访问点,他可以识别这个组织的负责人。
结构
- Singleton声明静态的方法getInstance(),这个方法返回相同的Singleton类实例。
Singleton的构造方法对客户端代码应当不可见。getInstance()应该是唯一的可以创建并获得Singleton对象的途径。
伪代码
在这个例子中,数据库连接类扮演Singleton角色。这个类没有公开的构造方法,所以只有调用getInstance方法可以获取这个对象。这个方法混存第一次创建的对象,在随后所有的调用中都返回它。
单例模式保证他的类只有一个实例被创建。并且,他提供了实例全局访问点:这个静态方法getInstance。
1 | class Database is |
适用性
当程序需要提供给所有客户端一个类的一个可以实例。比如,一个单独的数据库对象,在程序的不同模块贡献。
除了特别的的创建方法,Singleton对客户端隐藏了所有创建类的新对象的方法。这个方法创建一个新对象或者返回之前已经创建过的已经存在的对象。
当你需要严格控制全局变量。
不想全局变量,Singleton保证只有一个类实例。除了Singleton本身,没有任何类可以替换缓存的实例。
Sigleton让你可以轻松改变这个限制。比如,允许任何数量的实例,你只需要在一个地方修改代码–getInstance()方法体内。
如何实现
在类中添加一个静态字段用来持有单实例。
声明静态的公开创建方法,它将用来检索单实例。
在创建方法中实现“懒初始化”。它应该在第一次调用时创建一个新实例,并把它放到静态变量中。在随后的调用中这个方法都返回这个实例。
把类的构造方法声明为私有。
把客户端代码中所有直接对构造方法的调用替换为对创建方法的调用。
优点
保证类只有一个实例。
提供实例的全局访问点。
允许懒实例。
缺点
违背单一职责原则。
面具坏设计(Masks bad design?)。
在多线程的环境下需要特别处理。
在单元测试中要无尽mock。
和其他模式的关系
Facade可以改造成Singleton,因为大多情况下,一个门面对象就足够了。
在一些情况下Flyweight和Sigleton很像,Flyweight把什么事情都减少到一个享元对象。但是记住,它们之间有两个基本的不同:
1.Singleton对象时易变的。Flyweight对象时不变的。
- 单例类只有一个类实例,而享元类有多个不同状态的实例。
Abstract Factory,Builder和Prototype都可以实现为Singleton。
小结
Singleton在优缺点方面几乎和全局变量一样。尽管它们很好用,但却破坏了你代码的模块化。
在其他的上下文中,你可以使用一个依赖于Singleton的类。你将不得不携带Singleton类。大多数时候,在创建单元测试时会出现这个限制。
尽管许多开发着认为Singleton是反模式,但是在Java的核心类中也有许多例子:
java.lang.Runtime#getRuntime()
java.awt.Desktop#getDesktop()
java.lang.System#getSecurityManager()
Singleton可以通过一个返回相同缓存对象的静态创建方法来识别。