Design-Pattern-Singleton

单例模式(Singleton)是一种创建型模式。目的是,确保类只有一个实例, 并提供一个访问该实例的全局节点

会产生这样的需求,理由也十分自然:在全局范围内使用同一个对象或资源,方便统一配置与管理。典型的场景如:配置类、日志类、资源管理类(数据库连接池)等。

SpringBoot常见的分层架构中,Controller层、Service层通常不需要多个实例,因其成员变量通常为对其他Service的引用,几乎不包含状态信息。如果有多个实例,实例间也无法体现差异。因此在Spring框架中,容器中的各种bean默认是单实例的。并通过IoCDI,去组织不同单实例间的依赖关系。需要注意单实例不一定是单例模式,因为其并不是全局范围都能访问,而是通过DI组织了依赖关系。但如果是通过BeanFactory.getBean()来获取单实例,则和单例模式十分相似。

不同实例间的差异体现在其成员变量上。如果创建的多个实例成员变量都相同,并且实例在全局范围内有比较多使用的地方,则可以考虑使用单例模式。

单例模式VS静态公共方法

如果为了提供一些方法和变量的全局访问权限,在java中可以将这些方法和变量设置为public static,可以得到类似的效果。静态公共方法可以在任意位置通过类名.方法名调用。类的加载机制又保证了,在不同位置调用的方法,引用的都是相同的静态成员变量,模拟了只有一个实例的效果。

但这么做也多了很多限制:静态方法中只能引用静态变量;静态变量属于类而不是实例,无法被继承,也无法实现接口;无法通过多态来进行动态调用;无法控制其生命周期;无法进行垃圾回收;无法实现延迟初始化等等。

由于单例模式比静态公共方法灵活得多,因此更多使用单例模式来达成全局访问的效果。

单例模式VS全局变量

在一些面向对象语言中,如C++Python,可以通过全局变量的方式来建立全局访问的效果。通常我们认为全局变量是需要避免的。而单例模式,很多人认为其是对全局变量的一种包装,因此也反对使用单例模式。例如C++ Core Guidelines中就不建议使用单例模式

使用全局变量的缺点同样也是单例模式的缺点。例如全局范围内的访问和修改可能会导致全局变量的状态无法估计;需要考虑并发访问,线程安全性;使得模块之间的依赖关系变得隐匿(如两个模块共同与全局变量进行了交互),造成了模块间的耦合;使得单元测试变得更困难,无法mock替换;C++中无法保证全局变量初始化的顺序。

可以看出,这些缺点很多都与全局的写权限有关。因此单例模式如果不提供写的权限,或者不包含状态类型的变量(状态通常会在程序运行中改变),或者本身就是不可变对象,那么可以很好地规避上述缺点。此时单例模式的使用通常是合适且优雅的。

网上关于设计模式、反设计模式的讨论还有很多。程序员需要根据实际情况,来决定是否使用,而不是一昧支持或反对。

单例模式的通用实现

单例模式在不同语言中,根据每种语言的特性,有着不同的实现。在不同实现中,有一些共同之处。综合这些共同之处,便是一个通用实现。

为了符合单例模式确保类只有一个实例的特性,需要限制程序员在其他地方主动new实例的权限,把类的构造函数设置为private。而全局访问的特性,通常会在类内定义一个public static静态方法,返回一个类对象。

这个单例对象需要被某个变量引用。基于其全局唯一的特性,通常会定义为类的静态变量。并在静态方法中作为方法返回值。

基于上述描述,可以给出如下java代码:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static final Singleton INSTANCE = new Singleton();

private Singleton() {
// 私有构造方法防止外部实例化
}

public static Singleton getInstance() {
return INSTANCE;
}
}

其中,静态变量INSTANCE通过直接赋值来进行初始化。对于静态变量,初始化会在类被加载时执行。而类的加载机制保证了线程安全性,new Singleton()只会被执行一次,确保全局只会存在一个实例。

由于上述过程中,类被加载时就会完成实例的初始化,初始化的时机比较早,此方法被称作“饿汉式"。

与之相对的是,在getInstance()被首次调用时才完成初始化,初始化时机更晚,被称作“懒汉式”。在懒汉式中。由于静态方法可能被多线程同时调用,多个线程只能有一个完成对象的创建和初始化,需要在方法中通过锁机制,来保证线程安全性。关于如何实现线程安全太过细节,为了避免掩盖单例模式的特点,在此不再赘述。

总结

单例模式是一种创建型模式,旨在确保一个类只有一个实例,并提供一个全局访问点。

由于单例模式的全局性,通常会把其与全局变量比较。全局变量的缺点,可能在单例模式上也存在。通常可以通过在单例中不包含状态或提供改变状态的方法来避免这些缺点。程序员需要根据实际情况判断场景是否适合单例模式。

单例模式的通用实现的特点是,把构造函数私有化,以及提供一个公开静态函数。前者支持唯一性,后者提供全局访问性


Design-Pattern-Singleton
http://dracoyus.github.io/2024/05/15/Design-Pattern-Singleton/
作者
DracoYu
发布于
2024年5月15日
许可协议