作者按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用 和`python`兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :) 個人技術博客 "godbmw.com" 歡迎來玩! 每周至少 1 篇原創技術分享,還有開源教程(webpack、設計模式)、 ...
作者按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用
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)}\n下載了3個文件,但其實只創建了${objPool.size()}個對象`
),
1000
);
輸出結果如下:
+ 從 https://download1.com 開始下載 文件1
+ 從 https://download2.com 開始下載 文件2
- 文件1 下載完畢
- 文件2 下載完畢
+ 從 https://download3.com 開始下載 文件3
- 文件3 下載完畢
**************************************************
下載了3個文件,但其實只創建了2個對象
4. 參考
- 《JavaScript 設計模式和開發實踐》