单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。
亦称:单件模式、Singleton
1. 结构
单例(Singleton)类声明了一个名为 getInstance 获取实例的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端(Client) 代码隐藏。 调用获取实例 方法必须是获取单例对象的唯一方式。
2. 示例代码
2.1 懒汉式
public class Singleton {
/**
* 定义一个变量来存储创建好的类实例
*/
private static Singleton uniqueInstance = null;
/**
* 私有化构造方法
*/
private Singleton(){
}
/**
* 定义一个方法来为客户端提供类实例
* @return
*/
public static synchronized Singleton getInstance(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
/**
* 示意属性,单例可以有自己的属性
*/
private String singletonData;
public String getSingletonData(){
return singletonData;
}
}
2.2 饿汉式
public class Singleton2 {
/**
* 定义一个变量来存储创建好的类实例
*/
private static Singleton2 uniqueInstance = new Singleton2();
/**
* 私有化构造方法
*/
private Singleton2(){
}
/**
* 定义一个方法来为客户端提供类实例
* @return
*/
public static synchronized Singleton2 getInstance(){
//直接使用已经创建好的实例
return uniqueInstance;
}
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
/**
* 示意属性,单例可以有自己的属性
*/
private String singletonData;
/**
* 示意方法,让外部通过这些方法l来访问属性的值
* @return
*/
public String getSingletonData(){
return singletonData;
}
}
2.3 双重检查
public class Singleton3 {
/**
* 定义一个变量来存储创建好的类实例
*/
private static volatile Singleton3 uniqueInstance = null;
/**
* 私有化构造方法
*/
private Singleton3(){
}
/**
* 定义一个方法来为客户端提供类实例
* @return
*/
public static Singleton3 getInstance(){
if (uniqueInstance == null){
synchronized (Singleton3.class){
if (uniqueInstance == null){
uniqueInstance = new Singleton3();
}
}
}
return uniqueInstance;
}
}
3. 使用场景
如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。
单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象。
如果你需要更加严格地控制全局变量,可以使用单例模式。
单例模式与全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过任何方式替换缓存的实例。
请注意, 你可以随时调整限制并设定生成单例实例的数量,只需修改 获取实例 方法, 即 getInstance 中的代码即可实现。
4. 优缺点
优点 你可以保证一个类只有一个实例。
优点 你获得了一个指向该实例的全局访问节点。
优点 仅在首次请求单例对象时对其进行初始化。
缺点 违反了 单一职责原则。该模式同时解决了两个问题。
缺点 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
缺点 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
缺点 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。
5. 模式之间关系
- 在许多设计工作的初期都会使用工厂方法(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂、原型或生成器(更灵活但更加复杂)。
- 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。
- 你可以在创建复杂组合树时使用生成器,因为这可使其构造步骤以递归的方式运行。
- 你可以结合使用生成器和桥接模式: 主管类负责抽象工作,各种不同的生成器负责实现工作。
- 抽象工厂、生成器和原型都可以用单例来实现。