简介
享元模式,字面上的意思是分享元始数据,看起来和原型模式好像有些类似。但是实际上两者是不相同的。
原型模式是在已有的基础上进行深拷贝,生成一个全新的对象,对于这个全新的对象,我们可以随意修改。
享元模式则不然,享元模式对于同一种类型的实例,只会生成一个实例对象,区别只是实例对像的属性会有所改变。
举个例子:游戏中有很多树木场景,如果为每棵树都生成一个实例对象,那么一个地图中则需要生成成百上千个树实例,这严重的增加了系统的消耗。
因此,我们希望能将重复类型的树实例对象进行复用。
正文
实现享元模式的关键在于,要判断什么时候可以复用已有的实例对象,什么时候需要创建一个新的实例对象。
下面我们用一个例子来演示一下,如何判断是否生成新的实例。
import random
class Tree:
pool = {}
def __new__(cls,tree_type,age,*args,**kwarg):
obj = cls.pool.get(tree_type,None)
if not obj:
new_obj = super().__new__(cls,*args,**kwarg)
cls.pool[tree_type] = new_obj
return new_obj
else:
return obj
def __init__(self,*args,**kwarg):
for k, v in kwarg.items():
self.__setattr__(k,v)
def display(self):
print('the tree age is {}'.format(self.age))
for i in range(3):
t = Tree(tree_type='apple',age=random.randrange(1,100))
print(id(t),t)
t.display()
for i in range(3):
t = Tree(tree_type='pear',age=random.randrange(1,100))
print(id(t),t)
t.display()
从例子中,我们可以很明显的看出,通过pool池子的管理来决定是否需要生成新的实例对象。对于已经生成过的实例对象,我们只需要在初始化的时候赋予不同的值,便可以进行区分。
因此,我们需要重写new方法,从而达到自主控制实例生成的目的。
总结
大家应该还记得,在之前的模式中,也有一个模式需要重写new方法,从而达到全局只有一个实例的目的——单例模式。
享元模式和单例模式的构成有些类似,区别则是在于,单例模式下,所有类型只能有一个实例,但是享元模式是对于相同类型的实例只能有一个,不同的类型使用不同的实例。
因此享元模式在存在下列常见的时候,是比较适合使用的:
- 应用需要使用大量的对象
- 对象太多,存储/渲染它们的代价太大。一旦移除对象中的可变状态(因为在需要之时,应该由客户端代码显示地传递给享元),多组不同的对象可被相对更少的共享对象所替代。
- 对象ID对于应用不重要。对象共享会造成ID比较的失败,所以不能依赖对象ID(那些在客户端代码看来不同的对象,最终具有相同的ID)。(在例子中我们可以发现,后面的的初始化将前面的覆盖掉了)