我的部落格:github.com/ruizhengyun…
為什麼使用單體模式
在回答這個問題之前,先說下什麼是單例模式。 單例模式又叫單體模式,保證一個類僅有一個例項,這意味著第二次使用同一個類建立新物件時,得到的是與第一次所建立的物件完全相同,並提供全域性訪問點。
與全域性變數的是非
抓住關鍵詞 “唯一” 和 “全域性訪問” 的物件,不經讓我想起全域性物件。
// 全域性物件
var globaObj = {};
複製程式碼
but,使用全域性變數會有以下問題:
- 名稱空間汙染(變數名衝突)
- 維護時不方便管控(容易不小心覆蓋)
全域性變數問題折中的應對方案:
- 使用名稱空間
- 閉包封裝私有變數(利用函式作用域)
- ES6的 const/symbol
作用
- 能很好組織程式碼,便於維護和除錯,只例項化一次;
- 可生成自己的名稱空間,防止程式碼被篡改;
- 惰性例項化,需要一個物件的時候才建立它,有助於效能提升和減少不必要的記憶體消耗;
說明
- 單例模式需要用到 private 特性,但 ts 和 java 有,但 es6(javascript) 中沒有;
- 使用 java 程式碼來演示 UML 圖的內容;
程式碼演示
1.java 實現
// 0.0.3/Singleton.java
public class Singleton {
// 私有化建構函式,即外部不能使用 new Singleton(),外部不能使用new!!
private Singleton(){}
// 內部 new
private Singleton instance = null;
// 對外介面
public Singleton getInstance() {
if(instance === null) {
// 保證只會 new 一次
instance = new Singleton();
}
return instance;
}
//物件方法
public void show(name, pwd) {
System.out.printIn('展示');
}
}
public class SingletonDemo {
public static void main(String[] args) {
// 不合法
Singleton object = new Singleton();
// 正確使用,唯一可用可用物件
Singleton object = Singleton.getInstance();
object.show();
}
}
複製程式碼
2.javascript 簡單實現
使用一個變數儲存類例項物件(值初始為 null/undefined
)。進行類例項化時,判斷類例項物件是否存在,存在則返回該例項,不存在則建立類例項後返回。多次呼叫類生成例項方法,返回同一個例項物件。
// 0.0.3/Singleton.js
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
show() {
console.log(this.name);
}
}
Singleton.getInstance = function (name) {
if (this.instance) {
return this.instance;
}
return this.instance = new Singleton(name);
}
// 例項
// 只能使用靜態函式 getInstance,不能使用 new Singleton(),但是隻能文件約束
let s1 = Singleton.getInstance('展示1');
s1.show();
let s2 = Singleton.getInstance('展示2');
s2.show();
console.log(s1 === s2); // true
let s3 = new Singleton('展示3');
s3.show();
let s4 = new Singleton('展示4');
s4.show();
console.log(s3 === s4); // false
複製程式碼
上面 s1 === s2
為 true
,而 s3 === s4
為 false
,原因在於 s1
和 s2
在堆記憶體中指向同一地址, 而 s3
和
s4
在堆記憶體開闢了兩套空間。
存在問題
- 不夠透明,無法使用 new 類例項化,只能用文件約束呼叫方式
Singleton.getInstance(...)
; - 管理操作與物件建立的操作,功能程式碼耦合在一起,不符合**“單一職責原則”**;
3.javascript 透明實現
統一用 new
操作符獲取單例,而不是使用 Singleton.getInstance(...)
// 0.0.3/Singleton2.js
let Singleton = (function () {
let instance
return function (name) {
if (!instance) {
this.name = name;
return instance = this;
}
return instance;
}
})();
Singleton.prototype.show = function () {
console.log(this.name);
}
// 例項
let s3 = new Singleton('展示3');
s3.show();
let s4 = new Singleton('展示4');
s4.show();
console.log(s3 === s4); // true
複製程式碼
透明版解決了簡單版不夠“透明”的問題,又可以使用 new
操作符來建立例項物件,瞬間覺得天是藍色,這個顏色真美,看誰也都順眼了。
4.javascript 代理版
// 0.0.3/Singleton3.js
let SingletonProxy = (function () {
let instance
function main(name) {
if (!instance) {
return instance = new Singleton(name);
}
return instance;
}
return main
})();
let Singleton = function (name) {
this.name = name;
}
Singleton.prototype.show = function () {
console.log(this.name);
}
// 例項
const p1 = new SingletonProxy('代理1');
p1.show(); // 代理1
const p2 = new SingletonProxy('代理2');
p2.show(); // 代理1
console.log(p1 === p2); // true
複製程式碼
將管理單例操作,與物件建立操作進行拆分,實現更小的粒度劃分,符合“單一職責原則”。
實現過程
- 類的構造方法必須私有,不能被外界訪問;
- 使用類的靜態變數以標記例項物件是否已建立,當然該變數可以直接指向建立的例項物件;
- 使用類的靜態方法來返回和建立例項物件;
適用場景
1.模態框(登入框,資訊提升框)
// 0.0.3/SingletonModal.js
class Modal {
constructor() {
this.display = 'hide';
}
show() {
if (this.display === 'show') {
console.log('不可重複展示');
return
}
this.display = 'show';
console.log('成功展示');
}
hide() {
if (this.display === 'hide') {
console.log('不可重複隱藏');
return
}
this.display = 'hide';
console.log('成功隱藏');
}
}
Modal.getInstance = (function () {
let instance = null
return function () {
if (instance === null) {
instance = new Modal();
}
return instance;
}
})();
// 例項
let m1 = Modal.getInstance();
let m2 = Modal.getInstance();
m1.show();
m2.show();
m1.hide();
m2.hide();
console.log(m1 === m2);
複製程式碼
2.其他
- 引用第三方庫(多次引用只會使用一個庫引用,如 jQuery)
- 購物車(一個使用者只有一個購物車)
- 全域性態管理 store (Vuex / Redux)
專案中引入第三方庫時,重複多次載入庫檔案時,全域性只會例項化一個庫物件,如 jQuery,lodash,moment ...
, 其實它們的實現理念也是單例模式應用的一種:
// 引入程式碼庫 libs(庫別名)
if (window.libs != null) {
return window.libs; // 直接返回
} else {
window.libs = '...'; // 初始化
}
複製程式碼
設計原則驗證
- 符合單一職責原則,只例項化唯一的物件