作者按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用
javascript
和python
兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 ?
個人技術部落格-godbmw.com 歡迎來玩! 每週至少 1 篇原創技術分享,還有開源教程(webpack、設計模式)、面試刷題(偏前端)、知識整理(每週零碎),歡迎長期關注!本篇部落格地址是:《每天一個設計模式之享元模式》。
如果您也想進行知識整理 + 搭建功能完善/設計簡約/快速啟動的個人部落格,請直接戳theme-bmw
0. 專案地址
1. 什麼是“享元模式”?
享元模式:運用共享技術來減少建立物件的數量,從而減少記憶體佔用、提高效能。
- 享元模式提醒我們將一個物件的屬性劃分為內部和外部狀態。
- 內部狀態:可以被物件集合共享,通常不會改變
- 外部狀態:根據應用場景經常改變
- 享元模式是利用時間換取空間的優化模式。
2. 應用場景
享元模式雖然名字聽起來比較高深,但是實際使用非常容易:只要是需要大量建立重複的類的程式碼塊,均可以使用享元模式抽離內部/外部狀態,減少重複類的建立。
為了顯示它的強大,下面的程式碼是簡單地實現了大家耳熟能詳的“物件池”,以彰顯這種設計模式的魅力。
3. 程式碼實現
這裡利用python
和javascript
實現了一個“通用物件池”類–ObjectPool
。這個類管理一個裝載空閒物件的陣列,如果外部需要一個物件,直接從物件池中獲取,而不是通過new
操作。
物件池可以大量減少重複建立相同的物件,從而節省了系統記憶體,提高執行效率。
為了形象說明“享元模式”在“物件池”實現和應用,特別準備了模擬了File
類,並且模擬了“檔案下載”操作。
通過閱讀下方程式碼可以發現:對於File
類,內部狀態是pool
屬性和download
方法;外部狀態是name
和src
(檔名和檔案連結)。藉助物件池,實現了File
類的複用。
注:為了方便演示,Javascript
實現的是併發操作,Python
實現的是序列操作。輸出結果略有不同。
3.1 Python3 實現
from time import sleep
class ObjectPool: # 通用物件池
def __init__(self):
self.__pool = []
# 建立物件
def create(self, Obj):
# 物件池中沒有空閒物件,則建立一個新的物件
# 物件池中有空閒物件,直接取出,無需再次建立
return self.__pool.pop() if len(self.__pool) > 0 else Obj(self)
# 物件回收
def recover(self, obj):
return self.__pool.append(obj)
# 物件池大小
def size(self):
return len(self.__pool)
class File: # 模擬檔案物件
def __init__(self, pool):
self.__pool = pool
def download(self): # 模擬下載操作
print(`+ 從`, self.src, `開始下載`, self.name)
sleep(0.1)
print(`-`, self.name, `下載完成`)
# 下載完畢後,將物件重新放入物件池
self.__pool.recover(self)
if __name__ == `__main__`:
obj_pool = ObjectPool()
file1 = obj_pool.create(File)
file1.name = `檔案1`
file1.src = `https://download1.com`
file1.download()
file2 = obj_pool.create(File)
file2.name = `檔案2`
file2.src = `https://download2.com`
file2.download()
file3 = obj_pool.create(File)
file3.name = `檔案3`
file3.src = `https://download3.com`
file3.download()
print(`*` * 20)
print(`下載了3個檔案, 但其實只建立了`, obj_pool.size(), `個物件`)
輸出結果(這裡為了方便演示直接使用了sleep
方法,沒有再用多執行緒模擬):
+ 從 https://download1.com 開始下載 檔案1
- 檔案1 下載完成
+ 從 https://download2.com 開始下載 檔案2
- 檔案2 下載完成
+ 從 https://download3.com 開始下載 檔案3
- 檔案3 下載完成
********************
下載了3個檔案, 但其實只建立了 1 個物件
3.2 ES6 實現
// 物件池
class ObjectPool {
constructor() {
this._pool = []; //
}
// 建立物件
create(Obj) {
return this._pool.length === 0
? new Obj(this) // 物件池中沒有空閒物件,則建立一個新的物件
: this._pool.shift(); // 物件池中有空閒物件,直接取出,無需再次建立
}
// 物件回收
recover(obj) {
return this._pool.push(obj);
}
// 物件池大小
size() {
return this._pool.length;
}
}
// 模擬檔案物件
class File {
constructor(pool) {
this.pool = pool;
}
// 模擬下載操作
download() {
console.log(`+ 從 ${this.src} 開始下載 ${this.name}`);
setTimeout(() => {
console.log(`- ${this.name} 下載完畢`); // 下載完畢後, 將物件重新放入物件池
this.pool.recover(this);
}, 100);
}
}
/****************** 以下是測試函式 **********************/
let objPool = new ObjectPool();
let file1 = objPool.create(File);
file1.name = "檔案1";
file1.src = "https://download1.com";
file1.download();
let file2 = objPool.create(File);
file2.name = "檔案2";
file2.src = "https://download2.com";
file2.download();
setTimeout(() => {
let file3 = objPool.create(File);
file3.name = "檔案3";
file3.src = "https://download3.com";
file3.download();
}, 200);
setTimeout(
() =>
console.log(
`${"*".repeat(50)}
下載了3個檔案,但其實只建立了${objPool.size()}個物件`
),
1000
);
輸出結果如下:
+ 從 https://download1.com 開始下載 檔案1
+ 從 https://download2.com 開始下載 檔案2
- 檔案1 下載完畢
- 檔案2 下載完畢
+ 從 https://download3.com 開始下載 檔案3
- 檔案3 下載完畢
**************************************************
下載了3個檔案,但其實只建立了2個物件
4. 參考
- 《JavaScript 設計模式和開發實踐》