单例设计模式(Singleton)
单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。
案例说明
数据库连接
UML图
- 单例(Singleton)类声明了一个名为 getInstance 获取实 例 的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端(Client) 代码隐藏。 调用 获取实例 方法必须是获取单例对象的唯一方式。
核心代码
multithreaded/DatabaseMultiThreaded
/**
* @author: ccyy
* @create: 2021-10-13
* @description: 数据库类会对`getInstance(获取实例)方法
* 进行定义以让客户端在程序各处 都能访问相同的数据库连接实例。
**/
public final class DatabaseMultiThreaded {
/**
* 保存单例实例的成员变量必须被声明为静态类型。
*/
private static DatabaseMultiThreaded databaseMultiThreaded;
public String databaseName;
/**
* 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构造方法。
* @param databaseName
*/
private DatabaseMultiThreaded(String databaseName) {
// 确保在该线程等待解锁时,其他线程没有初始化该实例。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.databaseName = databaseName;
}
/**
* 用于控制对单例实例的访问权限的静态方法。
* @param databaseName
* @return
*/
public static DatabaseMultiThreaded getInstance(String databaseName){
DatabaseMultiThreaded result = databaseMultiThreaded;
if (result != null){
return result;
}
// 控制多线程并发情况
synchronized (DatabaseMultiThreaded.class){
if (databaseMultiThreaded == null){
databaseMultiThreaded = new DatabaseMultiThreaded(databaseName);
}
return databaseMultiThreaded;
}
}
}
singleThreaded/Database
/**
* @author: ccyy
* @create: 2021-10-13
* @description: 数据库类会对`getInstance(获取实例)方法
* 进行定义以让客户端在程序各处 都能访问相同的数据库连接实例。
**/
public final class Database {
/**
* 保存单例实例的成员变量必须被声明为静态类型。
*/
private static Database database;
public String databaseName;
/**
* 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构造方法。
* @param databaseName
*/
private Database(String databaseName) {
// 确保在该线程等待解锁时,其他线程没有初始化该实例。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.databaseName = databaseName;
}
/**
* 用于控制对单例实例的访问权限的静态方法。
* @param databaseName
* @return
*/
public static Database getInstance(String databaseName){
if (database == null){
database = new Database(databaseName);
}
return database;
}
}
Main
import com.ccyy.designPattern.creational.singleton.multithreaded.DatabaseMultiThreaded;
import com.ccyy.designPattern.creational.singleton.singleThreaded.Database;
/**
* @author: ccyy
* @create: 2021-10-13
* @description: 测试
**/
public class Main {
public static void main(String[] args) {
// 单线程单例模式测试
Database database1 = Database.getInstance("单线程1");
Database database2 = Database.getInstance("单线程2");
System.out.println(database1.databaseName);
System.out.println(database2.databaseName);
// 多线程测试
new Thread(() ->{
DatabaseMultiThreaded databaseMultiThreaded = DatabaseMultiThreaded.getInstance("多线程1");
System.out.println(databaseMultiThreaded.databaseName);
}).start();
new Thread(() ->{
DatabaseMultiThreaded databaseMultiThreaded = DatabaseMultiThreaded.getInstance("多线程2");
System.out.println(databaseMultiThreaded.databaseName);
}).start();
}
}
适用场景
- 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
- 单例模式禁止通过除特殊构建方法以外的任何方式来创建自 身类的对象。该方法可以创建一个新对象,但如果该对象已 经被创建,则返回已有的对象。
- 如果你需要更加严格地控制全局变量,可以使用单例模式。
- 单例模式与全局变量不同,它保证类只存在一个实例。除了 单例类自己以外,无法通过任何方式替换缓存的实例。
优缺点
优点:
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点:
- 违反了_单一职责原则_。该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计,比如程序各组件之间相互了解 过多等。
- 该模式在多线程环境下需要进行特殊处理,避免多个线程多 次创建单例对象。 单例的客户端代码单元测试可能会比较困难,因为许多测试 框架以基于继承的方式创建模拟对象。由于单例类的构造函 数是私有的,而且绝大部分语言无法重写静态方法,所以你 需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代 码,或者不使用单例模式。
与其他模式的关系
- 外观类通常可以转换为单例类,因为在大部分情况下一个外 观对象就足够了。
- 如果你能将对象的所有共享状态简化为一个享元对象,那么 享元就和单例类似了。但这两个模式有两个根本性的不同。 1. 只会有一个单例实体,但是享元类可以有多个实体,各实 体的内在状态也可以不同。 2. 单例对象可以是可变的。享元对象是不可变的。
- 抽象工厂、生成器和原型都可以用单例来实现。