設計模式之單例模式
單例模式的基本概念
單例模式是一種保證一個類僅有一個實例,並提供一個全局訪問點的設計模式,它還有些許其他的叫法,比如說懶漢模式、單子模式等。那麼這種設計模式解決了一個什麼事情呢?我們來看下這樣一段代碼
function Foo() {}
const s1 = new Foo();
const s2 = new Foo();
console.log(s1 === s2); // false
從結果上看,每通過構造函數創建一個對象,就會新開闢一片內存去存儲,所以兩個對象的值是不相等,而我們要做的事情是,讓它每次創建出來的結果都是同一個,那這就是單例模式,運用到現實生活中的場景,比如說全局狀態、前端頁面中的模態框等等。下面跟着單例模式的實現,我們來一步一步地改造它。
單例模式的實現
隨着 ECMAScript 標準的更新換代,最開始我們是通過函數 + 全局變量,或者函數加閉包的形式去實現單例模式,到後來 ES6 中有了 Class 的語法,我們可以用 Class 去寫單例,所以本文通過三種方式介紹單例模式的實現。
全局變量 + 函數
function Singleton() {}
Singleton.getInstance = function () {
if (!window.instance) {
window.instance = new Singleton();
}
return window.instance;
};
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
這裏思考下這樣寫有什麼不好的嗎?有的。例如程序員李雷在全局變量上掛載了一個 instance 屬性,程序員韓梅梅也在全局變量 windows 上掛載了一個 instance 屬性。他們互相都不告訴對方自己在 windows 上掛載了一個 instance 屬性,那麼這個時候是不是就會產生衝突呢?所以這樣子寫,不好。
函數 + 閉包
function Singleton() {}
Singleton.getInstance = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new Singleton();
}
return instance;
};
})();
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
ES 中面向對象的 Class
class Singleton {
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
單例模式的應用
單例模式作爲最簡單的設計模式之一,在軟件開發中應用也很廣泛,下面筆者結合自己的經歷,主要從前端和後端分別舉一個例子來介紹設計模式的應用。
單例模式在前端的應用
前面我們說了模態框,比如說,你頁面有個登錄按鈕,點擊後會彈出一個登錄框,這裏每次點擊登錄都重新彈一個新的模態框,顯然是不必要的,因爲他們內容是一樣的,所以我們期望把它緩存下來,核心代碼如下:
class Modal {
static getInstance() {
if (!Modal.instance) {
Modal.instance = new Modal();
Modal.instance.createElement();
}
return Modal.instance;
}
createElement() {
this.div = document.createElement('div');
this.div.id = 'modal';
this.div.innerHTML = '全局模態框';
this.div.style.display = 'none';
document.body.appendChild(this.div);
}
open() {
this.div.style.display = 'block';
}
close() {
this.div.style.display = 'none';
}
}
document.getElementById('BtnOpen').addEventListener('click', () => {
const modal = Modal.getInstance();
modal.open();
});
document.getElementById('BtnClose').addEventListener('click', () => {
const modal = Modal.getInstance();
modal.close();
});
具體的 demo 地址:https://zhengjiangtao.cn/show/design-mode/singleton.html
單例模式在後端的應用
這個是筆者在通過 nodejs 做微信開發的時候,藉助單例模式的思想優化相關的業務代碼的實踐所得,就是不能每次前端這邊來一個請求,或者別的地方引用或者使用到封裝的微信接口 API,就重新創建一個新的,那麼數據量上去了,這邊開銷是會很大的,比如百萬、千萬等等,所以我們期望把它緩存下來,然後用到直接取就好了。
// 創建一個微信公衆號相關的API類
class WechatOfficalAccountApi {
constructor(appId, appSecret, token) {
// code...
}
}
// 單例模式的實現
const createWechatOfficalAccountApi = (function (appId, appSecret, token) {
let instance = null;
return function () {
if (!instance) {
instance = new WechatOfficalAccountApi(appId, appSecret, token);
}
return instance;
};
})();
考慮到微信公衆號的類另有用途,所以就沒有都封裝到類裏面,而是單獨拋出一個函數去做這件事,大家想一下這樣寫好不好啊?是的,不好。問題就在於,比如說我創建了一個單例對象實例是去處理公衆號”江濤學編程 “的相關業務的,後來迫於生計,老闆決定賣藝,又搞了個” 江濤學音樂“,那麼這個時候你這個單例就歇菜了,因爲它只有一個實現例的全局訪問點,而 appid 每個微信公衆號都是不同的。
考慮到樓上這個場景,其實不能簡單地去像樓上去設計單例模式。我想到一個例子,就好比水產養殖這個專業,海王他就知道,單純地在池子裏養草魚,草魚會有點孤單,它會不會不快樂呢?它會不會絕食呢?於是它把龍蝦也放了進來,這樣子至少顯得不那麼孤單,可以聊聊天,龍蝦你今天喫什麼?草魚你今天喫什麼?池子裏充滿了歡聲笑語,哦,我明白了,我也給咱微信 API 接口造一個池子,開幹。
// 創建一個連接池
const wechatOfficalAccountApiPool = {};
// 創建一個微信公衆號相關的API類
class WechatOfficalAccountApi {
constructor(appId, appSecret, token) {
// code...
}
}
// 單例模式的實現
function createWechatOfficalAccountApi(appId, appSecret, token) {
let instance = wechatOfficalAccountApiPool[appId];
if (!instance) {
instance = new WechatOfficalAccountApi(appId, appSecret, token);
wechatOfficalAccountApiPool[appId] = instance;
}
if (instance.appSecret !== appSecret || instance.token !== token) {
throw new Error(
`createWechatOfficalAccountApi(${appId}, ${appSecret}, ${token}): ` +
`conflict with existing one: (${instance.appId}, ${instance.appSecret}, ${instance.token})`
);
}
return instance;
}
爲了更健壯魯棒一點,我們已知微信的 appid 是唯一的,就以它作爲 key 來搞,這樣子的話就可以處理多個業務場景了,比如老闆開了好多個媒體號,有 “江濤學編程”,” 江濤學音樂 “,” 江濤去旅行“等等,根據不同的業務場景和用途,就可以在最基礎的通用性強的微信接口 API 上去擴展實現對應的業務場景的功能。
參考文獻
- 維基百科 - 設計模式:https://zh.wikipedia.org/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F
原文地址:https://zhengjiangtao.cn/coding/singleton.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/UVSRsIIqjv6TWEVnSmGyOw