单例
单例
单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。单例模式主要解决了以下问题:
- 保证一个类只有一个实例,以控制某些共享资源(例如数据库或文件)的访问权限。
- 为该实例提供一个全局访问节点,和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。
如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。
优劣对比
单例模式的优点在于可以保证一个类只有一个实例,并且仅在首次请求单例对象时对其进行初始化。不过单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。并且该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法。
总结而言,单例模式还是适用于:
-
如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。单例模式禁止通过除了特殊创建方法以外的任何方式来创建自身类对象。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象。
-
如果你需要更加严格地控制全局变量,可以使用单例模式。单例模式与全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过任何方式替换缓存的实例,并且随时调整限制并设定生成单例实例的数量。
实现方式
所有单例的实现都包含以下两个相同的步骤:
-
将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。
-
新建一个静态创建方法作为构造函数。该函数会“偷偷”调用私有构造函数创建一个对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
即在类中添加一个私有静态成员变量用于保存单例实例,然后声明一个公有静态创建方法用于获取单例实例。在静态方法中实现"延迟初始化"。该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。然后将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用;检查客户端代码,将对单例的构造函数调用替换为对其静态创建方法调用。
案例:数据库连接
// 数据库类会对`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` 中将包含同一个的对象。