单例模式

什么是单例模式

单例模式是一种常见的软件设计模式,它提供了一种方法来确保一个类只有一个实例,并且提供了一个全局访问点来访问该实例。
在单例模式中,类有以下几个角色:

  • 单例类(Singleton):它是被控制实例数量的类。
  • 客户端(Client):它是使用单例的类。

例1

我们先来看一个简单的例子:
在前端开发中,你可以使用单例模式来创建一个全局的弹窗组件。这个弹窗组件可以在任何时候被调用,并且只会创建一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义单例类
class Popup {
// 声明私有的实例变量
private static instance: Popup;

// 私有构造函数,防止外部通过 new 关键字创建实例
private constructor() {}

// 公有的静态方法,用于获取单例的唯一实例
public static getInstance(): Popup {
// 如果实例不存在,则创建一个实例
if (!Popup.instance) {
Popup.instance = new Popup();
}
// 返回单例的唯一实例
return Popup.instance;
}

// 其他的公有方法,用于显示弹窗等功能
public show() {
console.log('显示弹窗');
}
}

// 客户端使用单例
const popup1 = Popup.getInstance();
const popup2 = Popup.getInstance();

// 两个弹窗对象都是同一个实例
console.log(popup1 === popup2); // true

// 调用弹窗的显示方法
popup1.show(); // 显示弹窗

在这个例子中,我们定义了一个 Popup 类,它有一个私有的实例变量 instance 和一个私有的构造函数,用于防止外部通过 new 关键字创建实例。此外在这个例子中,我们还定义了一个公有的方法 show,用于显示弹窗。

在客户端,我们通过调用 Popup.getInstance() 方法来获取单例的唯一实例。这样,无论你调用多少次 Popup.getInstance() 方法,都只会创建一个实例。

在这个例子中,我们通过比较两个弹窗对象是否相等,来验证单例模式是否正确实现。通过运行上面的代码,我们可以看到输出的结果为 true,说明两个弹窗对象都是同一个实例。

这就是一个使用单例模式创建全局弹窗组件的简单例子。

例2

在前端开发中,你可以使用单例模式来创建一个路由器组件。这个路由器组件可以帮助你管理页面之间的跳转,并且只会创建一个实例。

下面是使用 TypeScript 实现一个路由器组件的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义单例类
class Router {
// 声明私有的实例变量
private static instance: Router;

// 私有构造函数,防止外部通过 new 关键字创建实例
private constructor() {}

// 公有的静态方法,用于获取单例的唯一实例
public static getInstance(): Router {
// 如果实例不存在,则创建一个实例
if (!Router.instance) {
Router.instance = new Router();
}
// 返回单例的唯一实例
return Router.instance;
}

// 公有的跳转方法
public navigate(path: string) {
console.log(`跳转到 ${path} 页面`);
}
}

// 客户端使用单例
const router1 = Router.getInstance();
const router2 = Router.getInstance();

// 两个路由器对象都是同一个实例
console.log(router1 === router2); // true

// 调用路由器的跳转方法
router1.navigate('/home'); // 跳转到 /home 页面

在这个例子中,我们定义了一个 Router 类,它有一个私有的实例变量 instance 和一个私有的构造函数,用于防止外部通过 new 关键字创建实例。此外,我们还定义了一个公有的静态方法 getInstance,用于获取单例的唯一实例。在这个方法中,如果实例不存在,就会创建一个实例,然后返回单例的唯一实例。

在这个例子中,我们还定义了一个公有的方法 navigate,用于跳转到指定的页面。

在客户端,我们通过调用 Router.getInstance() 方法来获取单例的唯一实例。这样,无论你调用多少次 Router.getInstance() 方法,都只会创建一个实例。

在这个例子中,我们通过比较两个路由器对象是否相等,来验证单例模式是否正确实现。通过运行上面的代码,我们可以看到输出的结果为 true,说明两个路由器对象都是同一个实例。

单例模式的适用场景

单例模式适用的场景如下:

  • 当系统中只需要一个对象的时候,比如全局唯一的缓存对象、全局唯一的日志对象等。
  • 当系统中存在资源共享的对象时,比如数据库连接池、网络连接池等。
  • 当系统中需要限制对象数量的时候,比如许可证对象、限流器等。

单例模式可以节省系统资源,提升性能。但是,你需要注意,单例模式也会带来一些问题,比如对象的生命周期难以控制、测试困难等。所以,在使用单例模式时,你需要谨慎考虑它的优缺点,并确保它的使用符合单一职责原则。

遵循原则

单例模式遵循以下原则:

  • 单一职责原则:单例对象的职责应该单一,避免过度膨胀。
  • 开闭原则:单例对象应该对扩展开放,对修改关闭。
  • 依赖倒置原则:单例对象应该依赖于抽象,而不是具体实现。
  • 接口隔离原则:单例对象的接口应该尽量简洁,避免过多的依赖。