做者按:《天天一個設計模式》旨在初步領會設計模式的精髓,目前採用
javascript
和python
兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :)javascript
我的技術博客-godbmw.com 歡迎來玩! 每週至少 1 篇原創技術分享,還有開源教程(webpack、設計模式)、面試刷題(偏前端)、知識整理(每週零碎),歡迎長期關注!本篇博客地址是:《天天一個設計模式之享元模式》。前端
若是您也想進行知識整理 + 搭建功能完善/設計簡約/快速啓動的我的博客,請直接戳theme-bmwjava
享元模式:運用共享技術來減小建立對象的數量,從而減小內存佔用、提升性能。python
享元模式雖然名字聽起來比較高深,可是實際使用很是容易:只要是須要大量建立重複的類的代碼塊,都可以使用享元模式抽離內部/外部狀態,減小重複類的建立。webpack
爲了顯示它的強大,下面的代碼是簡單地實現了你們耳熟能詳的「對象池」,以彰顯這種設計模式的魅力。git
這裏利用python
和javascript
實現了一個「通用對象池」類--ObjectPool
。這個類管理一個裝載空閒對象的數組,若是外部須要一個對象,直接從對象池中獲取,而不是經過new
操做。es6
對象池能夠大量減小重複建立相同的對象,從而節省了系統內存,提升運行效率。github
爲了形象說明「享元模式」在「對象池」實現和應用,特別準備了模擬了File
類,而且模擬了「文件下載」操做。web
經過閱讀下方代碼能夠發現:對於File
類,內部狀態是pool
屬性和download
方法;外部狀態是name
和src
(文件名和文件連接)。藉助對象池,實現了File
類的複用。面試
注:爲了方便演示,Javascript
實現的是併發操做,Python
實現的是串行操做。輸出結果略有不一樣。
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 個對象
// 對象池 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個對象