核心概念
基本要求:
- 保证此系统中只有一个类实例
- 必须由创建本类自行创建
- 必须提供创建的方法
创建的两种方式:
- 饿汉式:在类加载时就创建好,
- 不会发生线程问题,创建速度快。
- 会消耗内存,一些无用的类也会被实例化。
- 懒汉式:是在调用时就会创建
- 可能回发生线程安全的问题,但在 js 中不会产生这种状况。
- 防止垃圾产生
类图
单例模式的类图比较简单,如下一个基本的单例模式类图:
在 java 里可以通过在类里面实例化类,来实现饿汉式的创建方法,但必须私有化类的构造方法,在提供返回实习的方法,懒汉式的实现时在提供的调用方法里判断是否已经实现,如过有则直接返回实例,否则实例化对象后再返回,但这样有锁就会产生线程安全问题,所以要加上同步锁关键字 synchronized 来解决线程安全问题。
javaScript 中无类加载的概念,可能不太能体现出单例模式的优势,但思想可以借鉴,
应用
单例模式的思想无非就是在整个系统中只向外提供一个实例,利用这种思想,我们可以对比其他场景中的应用。比如
- windows 在操作文件时,它就使用的是单线程,如果使用多线程的话,可能会造成意想不到的结果。
- 数据库生成唯一主键,如果使用多个实力去生成唯一主键,那写入的时候可能会出现错误的情况。
- 登录组件,弹框组件等这些系统想要单例的组件,如果调用时候会再生成一个,那就达不到我们想要的结果,我们可以想办法将这个组件单立起来,每次调用只返回已将创建好的的那一个。
要知道应用这些思想并不只适用于代码层面,也可以是模快之间,系统之间,服务器等。
栗子
唯一实例化
// 单例构造函数生成器
function SingletionNew(Constructor, ...params) {
let instance;
return function () {
if (instance){
return instance
}
return instance = new Constructor(...params)
}
}
// 想要生成唯一的 登录实例
let login = function (name) {
console.log('create')
this.name = name
this.login = function(){
console.log('login')
}
}
// 不通过单例构造器创建
let a = new login()
let b = new login()
// 生成多个实例
console.log(a === b) // false
// 通过单例构函数创建
let singletion_Login_Constructor = SingletionNew(login, 'tom')
// 生成一个能生成单例的构造函数
let singletionLoginA = singletion_Login_Constructor();
let singletionLoginB = singletion_Login_Constructor();
let singletionLoginC = singletion_Login_Constructor();
let singletionLoginD = singletion_Login_Constructor()
// 即时创建再多,也只会打印一次 create,,也就是只创一次
console.log(singletionLoginA === singletionLoginB) // true
唯一调用
// 单次调用方法生成器
function SingletionApply(fn, ...params) {
let result;
return function () {
if (result) return result
console.log(fn,params)
return result = fn(...params)
}
}
// 想要唯一调用的函数
let doneSomeThing = function (thing) {
console.log('havedoneSome:', thing)
return true // 返回 true 是为了做标记说已将创建了
}
// 生成唯一调用方法的函数
let singletion_login_call = SingletionApply(doneSomeThing, 'eat')
singletion_login_call()
singletion_login_call()
singletion_login_call()
// 多次调用控制台只会打印一次 havedoneSome: eat
可以看到,不管是方法调用还是实例生成,都是使用到了一个构造器,这个构造器的功能就是能够让这个构造方法生成单一实例,或生成一个唯一调用的方法 的一个类似于包装函数一样,这样可以让功能单一化,抽取共用的部分,达到复用的效果,遵守单一职责原理。
但美中不足的是,原对象还可以直接被改变,也没有提供向外获取实例的方法,所以这并不符合单一职责概念,只是是用了一些 js 中的特殊的“手段”使其产生单例模式的效果。
终极大招
使用装饰器模式模拟类加载过程,其接口定义为单例模式的类图,动态的扩展且不回改变对象原有的封装性符合开闭原则,这也是为什么我不使用函数内变量来模拟私有变量的原因。
function Login(name) {
this.name = name
// 自己的业务逻辑
}
// 要单例化的对象target,及创建时传参params
// 返回值覆盖原来的对象
function SingletionDecorater(target, ...params) {
// 为类对象上添加实例化对象
target.getInstance = function () {
return target.instance
}
target.instance = new target(...params) // 实例化一次
// 添加权限
target = new Proxy(target, {
set(target, key, value) { // 设置只读属性
if (key === 'instance' || key === 'getInstance') {
console.warn(`warn:can not set a new value for ${key}`)
return
}
target[key] = value
}, // 调用 new 时返回创建好的实例
construct(target) {
return target.instance
}
})
return target
}
// 使用 SingletionDecorater 装饰的函数,保证三大要点
Login = SingletionDecorater(Login, 'login')
// 尝试修改
Login.instance = 555 // can not set a new value for instance
let login = Login.getInstance() // Singletion { name: 'Login' }
let a = new Login('Login1') // 无论怎么创建都返回第一次创建的
let c = new Login('Login2') // Singletion { name: 'Login' }
console.log(a) //Singletion { name: 'Login' }
console.log(c === a)// true
console.log(c === login) // true
可以看到 Login = SingletionDecorater(Login, 'login')
类似 java 中创建单例对象的类加载过,并且此过程可懒汉,可饿汉,根据自己的装饰时机实现不同的效果。
这样就暂且完美的解决了单例模式不能再 JavaScript 中完美体现的问题,更多装饰器模式相关请移步装饰器章节。
为什么不适用 JS 内置修饰器,是因为我的 node 版本较低,当然,其原理都是一样的。
参考:
JavaScript 设计模式与开发实战 -- 曾探
JavaScript 设计模式 -- 张容铭
设计模式实训 --刘伟
文章有不妥之处还请指出,持续更新
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!