前言
先提出一个问题,为什么要学习设计模式
?
难道是提出一个代码形容词
,是为了让代码看起高大上 or 装逼
?
先看下设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
。
我的个人理解就是,是为了让你的代码变得简单而优雅
。有很高的可重用性
、可维护性
以及可扩展性
。
设计模式有很多种,我们今天先来盘一下最常用和经典的设计模式之一:单例模式
。
单例模式定义
单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
其实就是,第一次访问会进行初始化,创建一个实例,后面访问的时候拿到的都是这个实例,不会再重新创建
。
js
class Singleton {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
static getInstance(name) {
return this.instance || (this.instance = new Singleton(name))
}
}
const instance1 = Singleton.getInstance('zs')
const instance2 = Singleton.getInstance('lisi')
console.log(instance1 === instance2) // true
在单例类Singleton
上定义一个获取实例的getInstance
方法,第一次调用getInstance
的时候创建一个Singleton
实例保存在instance
属性上,后面再获取就直接取this.instance
。
但是这样会出现一个问题,用户如果不调用getInstance
,而直接去new Singleton
,这样还是会创建多个实例
出来,这样不是我们所期望的。
优化版
我们定义一个Singleton
类,并用一个instance
变量来保证在new 多次
时,全局Singleton
类实例的唯一性。
js
let instance = null;
class Singleton {
constructor(name) {
this.name = name;
if (!instance) {
instance = this;
}
return instance;
}
}
const instance1 = new Singleton('zs')
const instance2 = new Singleton('lisi')
console.log(instance1 === instance2) // true
更优雅的实现:使用代理模式实现单例
其实我们可以用ES6的proxy
来实现单例,其
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
function singleton(className) {
let instance = null;
return new Proxy(className, {
construct(target, args) {
if (!instance) {
instance = Reflect.construct(target, args);
}
return instance;
}
})
}
const ProxyPerson = singleton(Person);
const person1 = new ProxyPerson();
const person2 = new ProxyPerson();
console.log(person1 === person2)
代理模式实现单例的好处
- 解耦和单一职责原则 :我们使用了代理之后,等于在我们使用的客户端和实际对象两者中间,加入了一层
代理对象
,而代理对象相当于一个黑盒子,而对我我们客户端来说,使用的时候不关心他里面的逻辑和实现细节,只关心它给我们提供了哪些功能和接口,调用就完事了, 这样遵循了单一职责原则
,提高了代码的可维护性。 - 控制访问和延迟加载 :其实也可以说就是安全性,在代理层做校验可以说是再好不过了,可以先把一些错误情况给拦截掉,起到
访问控制
的效果,然后也可以根据情况,决定时候延迟创建实例
,也就是我们说的惰性单例
,这样对于CPU密集型
的实例来说,同时也能大大提高性能
。 - 扩展性 :我们可以在
代理层
通过继承或实现相同的接口
来扩展实际对象的功能,这样就可以做到不修改实际对象的源码,又增加上了扩展功能。
单例模式的应用
假如我们要创建一个全局唯一的弹框
,我们很容易先写在创建弹框的代码:
js
const div = document.createElement('div')
div.innerHTML = '我是全局唯一的弹框'
div.style.display = 'none'
document.body.appendChild(div)
创建弹框我们有两种思路,我们可以在一开始渲染页面
的时候就创建这个弹框,然后通过控制display
进行显示隐藏,当然也可以惰性的懒加载,第一次用到的时候再去创建弹框
,为了性能考虑,我们当然是选择后者。
html
<html>
<body>
<button id="btn">显示弹框</button>
</body>
<script>
const getSingle = (fn) => {
let instance = null
return (...args) => {
return instance || (instance = fn.apply(this, args))
}
}
const createModel = () => {
const div = document.createElement('div')
div.innerHTML = '我是全局唯一的弹框'
div.style.display = 'none'
document.body.appendChild(div)
return div;
}
const createSingleModel = getSingle(createModel);
btn.addEventListener('click', () => {
const model = createSingleModel();
model.style.display = 'block';
})
</script>
</html>
我们先把创建弹框的逻辑抽离到createModel
方法中,然后我们创建一个管理单例
的方法getSingle
,先调用getSingle
,将createModel
交给getSingle
去管理,这样就实现了唯一性
,然后在按钮点击的时候,在调用getSingle
返回的方法就行啦。
小结
上面介绍Javascript
最经典的设计模式之一单例模式
,简单来说,单例
就是单实例
,就是全局只能被创建一次,我们还用了代理对象
来实现单例,用以增加其维护性
和扩展性
。
关于单例模式
,大家有什么更好的实现方案呢?欢迎大家一起来讨论。