单例模式
亦称:单件模式、Singleton
意图
单例模式是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。
问题
单例模式同时解决了两个问题,所以违反了单一职责原则:
-
保证一个类只有一个实例。为什么会有人想要控制一个类所拥有的实例数量?最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。
它的运作方式是这样的:如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,此时你会获得之前已创建的对象,而不是一个新对象。
注意,普通构造函数无法实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象。
客户端甚至可能没有意识到它们一直都在使用同一个对象。
-
为该实例提供一个全局访问节点。还记 得你 (好吧,其实是我自己) 用过的那些存储重要对象的全局变量吗?它们在使用上十分方便,但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。
和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。
还有一点:你不会希望解决同一个问题的代码分散在程序各处的。因此更好的方式是将其放在同一个类中,特别是当其他代码已经依赖这个类时更应该如此。
如今,单例模式已经变得非常流行,以至于人们会将只解决上文描述中任意一个问题的东西称为单例。
解决方案
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有,防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。
真实世界类比
政府是单例模式的一个很好的示例。一个国家只有一个官方政府。不管组成政府的每个人的身份是什么,“某政府”这一称谓总是 鉴别那些掌权者的全局访问节点。
单例模式结构
-
单例 (Singleton) 类声明了一个名为
getInstance
获取实例的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端 (Client) 代码隐藏。调用
获取实例
方法必须是获取单例对象的唯一方式。
伪代码
在本例中,数据库连接类即是一个单例。该类不提供公有构造函数,因此获取该对象的唯一方式是调用 获取实例
方法。该方法将缓存首次生成的对象,并为所有后续调用返回该对象。
// 数据库类会对 `getInstance(获取实例)` 方法进行定义以让客户端在程序各处
// 都能访问相同的数据库连接实例。
class Database is
// 保存单例实例的成员变量必须被声明为静态类型。
private static field instance: Database
// 单例的构造函数必须永远是私有类型,以防止使用 `new` 运算符直接调用构
// 造方法。
private constructor Database() is
// 部分初始化代码(例如到数据库服务器的实际连接)。
// ……
// 用于控制对单例实例的访问权限的静态方法。
public static method getInstance() is
if (Database.instance == null) then
acquireThreadLock() and then
// 确保在该线程等待解锁时,其他线程没有初始化该实例。
if (Database.instance == null) then
Database.instance = new Database()
return Database.instance
// 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。
public method query(sql) is
// 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以
// 在这里添加限流或缓冲逻辑。
// ……
class Application is
method main() is
Database foo = Database.getInstance()
foo.query("SELECT ……")
// ……
Database bar = Database.getInstance()
bar.query("SELECT ……")
// 变量 `bar` 和 `foo` 中将包含同一个对象。