设计模式:单例模式

前言

学习了这么长的时间,设计模式却还是浅尝辄止,而且最近在回顾Vue知识的时候,发现了很多常见的设计模式:观察者啊,单例啊什么的。让我意识到,如果想要去读懂Vue底层的话,设计模式的思想是必不可少的。
这就从简单的单例模式开始,学习学习设计模式

什么是单例模式

字面上来看,就是只有一个实例的模式。简单来说,单例模式就是在整个系统中,保持一个实例,不生成新实例的模式。当然并不是说仅仅只能new一个对象出来,而是说,针对某个特殊的对象,我们在第一次将它实例化后,以后再次访问时,访问到的一直都是之前实例化好的特殊对象

为什么需要单例模式

正如上面说的那样,单例模式针对的是某些特殊的对象,那么该对象为什么被针对呢?

需要单例模式实现的对象系统,一般具有如下特点

对象

  • 需要频繁的被创建、销毁,但在这些过程中无法优化
  • 需要比较多的资源:读取配置、产生其他依赖对象

环境

  • 希望节省内存
  • 希望节省系统性能的开销
  • 希望某些资源不被多重占用
  • 希望有一个能够统一处理资源和优化的对象

VuexSpring中的Bean对象就是如此

通过单例模式,我们能够简单的实现节省性能开销、节省内存、统一管理资源等功能。因为只有一个实例对象存在,并不会新生成。

如何实现单例模式

单例模式的实现要点

  • 保证对象只有一个,不能生成新对象出来

JavaScript

实现的条件还是不变,保证对象只有一个,不能生成新对象出来。
在JavaScript中,如果要实现私有化,首先想到的就是闭包了,闭包还能够使该实例不消失。
其次结合if判断语句,如果实例存在,直接返回,不存在就执行new运算符初始化实例。

闭包实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let SingleCase = (function Singleton (){
let instance = null
function _init(){
// 此处可以定义一些初始化配置
}
return function(){
console.log(instance)
if(!instance){
instance = new _init()
}
return instance
}
})()

var a = SingleCase()
var b = SingleCase()
a === b // true

其实js中window顶层对象就是个单例了。

Es6 Class实现

首先我们要明白,Class是一个语法糖,本质上也是一个构造函数。
回顾单例的要求,一是共享一个实例,二是不能生成新实例出来

构造函数 constructor

Class有一个特点,在我们实例化Class时,会自动执行其内的constructor函数(每一个Class都有该函数,不声明默认为空。),可以初始化一些配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton{
constructor() {
if(!Singleton.instance) {
// 进行一些初始化操作
this.name = '张三'
Singleton.instance = this
}
return Singleton.instance
}
}
var a = new Singleton()
var b = new Singleton()
a === b // true

上面的方法能够实现单例的要点在于,只有第一个new Singleton()是真真的new出来的,之后的其实都是第一个new出来的实例对象。
通过以下输出可以证明

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton {
constructor() {
if(!Singleton.instance) { // 判断有无实例存在,若无则返回当前实例
window.first = this // 存储第一个实例
Singleton.instance = this // 将第一个实例赋值给Singleton.instance
}
window.second = this // 记录最新创建的实例
return Singleton.instance // 返回第一个实例
}
}
var a = new Singleton()
var b = new Singleton()
window.first === window.second // false

之所以该实例能够一直存在,是因为不仅仅后续创建的变量在引用它,而且Singleton本身的属性instance也在引用它,所以不会被销毁

静态方法/属性

在Es6中,Class提供了静态方法。表示该方法为该类独有,不会被实例继承。我们可以通过静态方法来实现单例

1
2
3
4
5
6
7
8
9
10
// 静态方法
class Singleton{
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
}
Singleton.getInstance() === Singleton.getInstance() // true

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性

1
2
3
4
5
6
7
// 静态属性(不是很确定这么用行不行,但是也能实现)
class Singleton {
static SingleCase = {};
}
var a = Singleton.SingleCase
var b = Singleton.SingleCase
a === b // true

Vuex的实现思路
by——Controllerszzy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SingleState {
// 状态存储机制
data={}
// 获取对象
get(key){
return this.data[key]||''
}
// 存储对象
set(key,value){
return this.data[key]=value
}
// 外部调用此函数实例化
static getInstance() {
if (!SingleState.instance) {
SingleState.instance = new SingleState
}
return SingleState.instance
}
}
const state_1 = SingleState.getInstance()
state_1.set('hi','hello') // 设置 key = hi value = hello
const state_2 = SingleState.getInstance()
console.log(state_2.get('hi')) // hello

使用过程中通过 Vue.use(Vuex) 安装了Vuex插件,而Vuex 插件是一个对象,它在内部实现了一个 install 方法,这个方法会在插件安装时被调用,从而把 Store 注入到Vue实例里去。也就是说每 install 一次,都会尝试给 Vue 实例注入一个 Store。

单例模式的优劣总结

优点

  • 减少了内存开支
  • 减少了系统的性能开销
  • 避免对资源的多重占用
  • 设置全局的访问点, 优化和共享资源访问

缺点

  • 扩展很困难
  • 对测试是不利的
  • 与单一职责原则有冲突
    • 一个类应该只实现一个逻辑, 而不关心它是否是单例的, 是不是要单例取决于环境, 单例模式把“要单例”和业务逻辑融合在一个类中

对ES6中新增类型Symbol的疑问

Symbol的实现或者用法和单例有什么联系呢?感觉上挺像的,虽然只是用于防止命名冲突。Es6一直不太会用出来,所以理解的也不是很到位。

总结

其实单例模式还区分饿汉懒汉两种模式,其差别在于:
饿汉要求直接生成实例,不管有没有调用该对象
懒汉要求什么时候用到了,什么时候再生成这个实例对象


补充

学习了js一段时间后,发现之前js实现的单例是比较局限的,自己并没有从一个前端的角度去思考设计模式如何实现。所以在阅读了《JavaScript设计模式与开发实践》后,来补充下最新的理解。

上述闭包实现的单例存在着一些问题

单例的判断逻辑和单例对象的创建混杂在了一起。假如想去创建其他非单例对象时,需要重新在再写一部分相同的创建逻辑,所以,我们需要把二者拆分开,以实现 单一职责原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 单例逻辑的控制
const getSingle = (fn)=> {
let result;
return function(){
return result || (result = fn.call(result,arguments));
}
}

// 可复用的创建各种各样东西的方法
const createObj = ()=> {discribe:'创建对象的方法'};
const createDiv = ()=> {discribe:'创建Div的方法'};

// 通过高阶函数,这样就准备好了创建单例的方法
const createSingleObj = getSingle(createObj);
const createSingleDiv = getSingle(createDiv );

const a = createSingleObj();
const b = createSingleObj();
console.log(a === b) // true
作者

徐云飞

发布于

2023-02-05

更新于

2023-02-05

许可协议

评论