享元模式:运用共享技术来减少创建对象的数量,从而减少内存占用、提高性能。
享元模式虽然名字听起来比较高深,但是实际使用非常容易:只要是需要大量创建重复的类的代码块,均可以使用享元模式抽离内部/外部状态,减少重复类的创建。
为了显示它的强大,下面的代码是简单地实现了大家耳熟能详的“对象池”,以彰显这种设计模式的魅力。
这里利用python
和javascript
实现了一个“通用对象池”类--ObjectPool
。这个类管理一个装载空闲对象的数组,如果外部需要一个对象,直接从对象池中获取,而不是通过new
操作。
对象池可以大量减少重复创建相同的对象,从而节省了系统内存,提高运行效率。
为了形象说明“享元模式”在“对象池”实现和应用,特别准备了模拟了File
类,并且模拟了“文件下载”操作。
通过阅读下方代码可以发现:对于File
类,内部状态是pool
属性和download
方法;外部状态是name
和src
(文件名和文件链接)。借助对象池,实现了File
类的复用。
注:为了方便演示,Javascript
实现的是并发操作,Python
实现的是串行操作。输出结果略有不同。
from time import sleepclass 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 = pooldef 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个对象