單例模式多是設計模式裏面最簡單的模式了,雖然簡單,但在咱們平常生活和編程中卻常常接觸到,本節咱們一塊兒來學習一下。
單例模式 (Singleton Pattern)又稱爲單體模式,保證一個類只有一個實例,並提供一個訪問它的全局訪問點。也就是說,第二次使用同一個類建立新對象的時候,應該獲得與第一次建立的對象徹底相同的對象。
在相似場景中,這些例子有如下特色:
function ManageGame(){ if(ManageGame._schedule){ // 判斷是否已經有單例了 return ManageGame._schedule } ManageGame._schedule = this } ManageGame.getInstance = function(){ if(ManageGame._schedule){ // 判斷是否已經有單例了 return ManageGame._schedule } return ManageGame ._schedule =new ManageGame() } const schedule1 = new ManageGame() const schedule2 =ManageGame.getInstance() console.log(schedule1===schedule2)
ts的 class 改造
class ManageGame{ private static schedule: any = null; static getInstance() { if (ManageGame.schedule) { // 判斷是否已經有單例了 return ManageGame.schedule } return ManageGame.schedule = new ManageGame() } constructor() { if (ManageGame.schedule) { // 判斷是否已經有單例了 return ManageGame.schedule } ManageGame.schedule = this } } const schedule1 = new ManageGame() const schedule2 = ManageGame.getInstance() console.log(schedule1 === schedule2)// true
缺點:上面方法的缺點在於維護的實例做爲靜態屬性直接暴露,外部能夠直接修改。
根據上面的例子提煉一下單例模式,遊戲能夠被認爲是一個特定的類(Singleton),而存檔是單例(instance),每次訪問特定類的時候,都會拿到同一個實例。主要有下面幾個概念:
const Singleton = (function() { let _instance = null // 存儲單例 const Singleton = function() { if (_instance) return _instance // 判斷是否已有單例 _instance = this this.init() // 初始化操做 return _instance } Singleton.prototype.init = function() { this.foo = 'Singleton Pattern' } return Singleton })() const visitor1 = new Singleton() const visitor2 = new Singleton() console.log(visitor1 === visitor2) // true
const Singleton = (function() { let _instance = null // 存儲單例 const Singleton = function() { if (_instance) return _instance // 判斷是否已有單例 _instance = this this.init() // 初始化操做 return _instance } Singleton.prototype.init = function() { this.foo = 'Singleton Pattern' } Singleton.getInstance = function() { if (_instance) return _instance _instance = new Singleton() return _instance } return Singleton })() const visitor1 = new Singleton() const visitor2 = new Singleton() // 既能夠 new 獲取單例 const visitor3 = Singleton.getInstance() // 也能夠 getInstance 獲取單例 console.log(visitor1 === visitor2) // true console.log(visitor1 === visitor3) // true
let getInstance { let _instance = null // 存儲單例 const Singleton = function() { if (_instance) return _instance // 判斷是否已有單例 _instance = this this.init() // 初始化操做 return _instance } Singleton.prototype.init = function() { this.foo = 'Singleton Pattern' } getInstance = function() { if (_instance) return _instance _instance = new Singleton() return _instance } } const visitor1 = getInstance() const visitor2 = getInstance() console.log(visitor1 === visitor2)
以前的例子中,單例模式的建立邏輯和原先這個類的一些功能邏輯(好比 init 等操做)混雜在一塊兒,根據單一職責原則,這個例子咱們還能夠繼續改進一下,將單例模式的建立邏輯和特定類的功能邏輯拆開,這樣功能邏輯就能夠和正常的類同樣。
/* 功能類 */ class FuncClass { constructor(bar) { this.bar = bar this.init() } init() { this.foo = 'Singleton Pattern' } } /* 單例模式的賦能類 */ const Singleton = (function() { let _instance = null // 存儲單例 const ProxySingleton = function(bar) { if (_instance) return _instance // 判斷是否已有單例 _instance = new FuncClass(bar) return _instance } ProxySingleton.getInstance = function(bar) { if (_instance) return _instance _instance = new Singleton(bar) return _instance } return ProxySingleton })() const visitor1 = new Singleton('單例1') const visitor2 = new Singleton('單例2') const visitor3 = Singleton.getInstance() console.log(visitor1 === visitor2) // true console.log(visitor1 === visitor3) // true
/* Person 類 */ class Person { constructor(name, age) { this.name = name this.age = age } } /* 單例模式的賦能方法 */ function Singleton(FuncClass) { let _instance return new Proxy(FuncClass, { construct(target, args) { return _instance || (_instance = Reflect.construct(FuncClass, args)) // 使用 new FuncClass(...args) 也能夠 } }) } const PersonInstance = Singleton(Person) const person1 = new PersonInstance('張小帥', 25) const person2 = new PersonInstance('李小美', 23) console.log(person1 === person2) // true
有時候一個實例化過程比較耗費性能的類,可是卻一直用不到,若是一開始就對這個類進行實例化就顯得有些浪費,那麼這時咱們就可使用惰性建立,即延遲建立該類的單例。以前的例子都屬於惰性單例,實例的建立都是 new 的時候才進行。前端
惰性單例又被成爲懶漢式,相對應的概念是餓漢式:vue
class FuncClass { constructor() { this.bar = 'bar' } } // 餓漢式 const HungrySingleton = (function() { const _instance = new FuncClass() return function() { return _instance } })() // 懶漢式 const LazySingleton = (function() { let _instance = null return function() { return _instance || (_instance = new FuncClass()) } })() const visitor1 = new HungrySingleton() const visitor2 = new HungrySingleton() const visitor3 = new LazySingleton() const visitor4 = new LazySingleton() console.log(visitor1 === visitor2) // true console.log(visitor3 === visitor4) // true
ts實現
懶漢式單例react
class LazySingleton{ private static instance:LazySingleton = null; private constructor(){ //private 避免類在外部被實例化 } public static getInstance():LazySingleton{ if (LazySingleton.instance == null) { LazySingleton.instance = new LazySingleton(); } return LazySingleton.instance; } someMethod() {} } let someThing = new LazySingleton(); // Error: constructor of 'singleton' is private let instacne = LazySingleton.getInstance(); // do some thing with the instance
用懶漢式單例模式模擬產生美國當今總統對象。
分析:在每一屆任期內,美國的總統只有一人,因此本實例適合用單例模式實現,圖 2 所示是用懶漢式單例實現的結構圖。
class SingletonLazy{ public static main(arg) { let zt1 = President.getInstance(); zt1.getName(); let zt2 = President.getInstance(); zt2.getName(); if (zt1 === zt2) { console.log("他們是同一我的"); } else { console.log("他們不是同一人"); } } } class President{ private static instance: President = null; private constructor() { console.log("產生一個總統了"); } public static getInstance():President{ if (President.instance == null) { President.instance = new President(); } else { console.log("已經有了一個總統了,不能產生新總統!"); } return President.instance; } public getName():void { console.log("我是美國總統:特朗普。"); } }
namespace 餓漢式{ class HungrySingleton{ private static instance: HungrySingleton = new HungrySingleton(); private constructor() { } public static getInstance(): HungrySingleton{ return HungrySingleton.instance; } } let someThing = new HungrySingleton(); // Error: constructor of 'singleton' is private let instacne = HungrySingleton.getInstance(); // do some thing with the instance }
以 ElementUI 爲例,ElementUI 中的全屏 Loading 蒙層調用有兩種形式:
// 1. 指令形式 Vue.use(Loading.directive) // 2. 服務形式 Vue.prototype.$loading = service
用服務方式使用全屏 Loading 是單例的,即在前一個全屏 Loading 關閉前再次調用全屏 Loading,並不會建立一個新的 Loading 實例,而是返回現有全屏 Loading 的實例。vue-router
下面咱們能夠看看 ElementUI 2.9.2 的源碼是如何實現的,爲了觀看方便,省略了部分代碼:
import Vue from 'vue' import loadingVue from './loading.vue' const LoadingConstructor = Vue.extend(loadingVue) let fullscreenLoading const Loading = (options = {}) => { if (options.fullscreen && fullscreenLoading) { return fullscreenLoading } let instance = new LoadingConstructor({ el: document.createElement('div'), data: options }) if (options.fullscreen) { fullscreenLoading = instance } return instance } export default Loading
單列模式的特色
單例模式主要解決的問題就是節約資源,保持訪問一致性。
簡單分析一下它的優勢:
單例模式也是有缺點的
單例模式的使用場景那咱們應該在什麼場景下使用單例模式呢: