享元(Flyweight)模式为相似对象引入数据共享以最小化使用内存,即可提供状态独立不可变之数据共享对象以促使提升性能。由於享元主旨在数据共享和优化内存性能,故對嵌入式系统(如手机、平板电脑和微控制器等)與性能具切切关键的应用(游戏、3D图形处理和实时系统等)都能从其获益,一般在應用開發上要使用享元模式,通常需满足以下几个条件:
- 當应用需要使用大量的对象。
- 當有多組对象則储存或渲染它们的代价太大,一旦可移除对象中的可变状态之考量(*這可在需要时由客户端以显式传递给享元),此時多组不同的对象可被相对更少的共享对象所替代。
- 當对象ID对于应用不重要時(*因对象共享会造成ID比较的失败,所以不能依赖对象ID,對客户端而言不同的对象始终具有相同的ID)
示例:
import random
from enum import Enum
# 以Enum类型描述三种不同种类的水果树
Treetype = Enum('TreeType', 'apple_tree cherry_tree peach_tree')
class Tree:
"""
此處pool变量是作為缓存对象池且須為类属性(*因要作為类的所有实例之共享变量),並使用__new__方法(*这个方法在__init__之前會被调用)把此类变换成元类以可支持自引用,即意味cls引用的是Tree本类。之後当客户端要创建Tree类的一个实例时,則以tree_type参数传递树的种类,此树的种类須检查是否已被创建對象,如果是则返回之前创建的对象;否则将这个新的树种添加到池中并返回相应的新对象。
"""
pool = dict()
def __new__(cls, tree_type): #重點:同單例模式之作法
obj = cls.pool.get(tree_type,None)
if not obj:
obj = object.__new__(cls)
cls.pool[tree_type] = obj
obj.tree_type = tree_type
return obj
def render(self, age, x, y):
"""
在屏幕上打印树之訊息,此在應用開發上應是作為渲染之用途。
"""
print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))
def main():
rnd = random.Random()
age_min, age_max = 1, 30 #定義树的年龄是介於1~30年之间的随机值
min_point, max_point = 0, 100 #定義坐标是介於1~100之间的随机值
tree_counter = 0 #树的數量
for _ in range(3):
t1 = Tree(Treetype.apple_tree)
t1.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point))
tree_counter += 1
for _ in range(5):
t2 = Tree(Treetype.cherry_tree)
t2.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point))
tree_counter += 1
for _ in range(7):
t3 = Tree(Treetype.peach_tree)
t3.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point))
tree_counter += 1
print('trees rendered: {}'.format(tree_counter))
print('trees actually created: {}'.format(len(Tree.pool)))
if __name__ == '__main__':
main()
輸出:
render a tree of type TreeType.apple_tree and age 3 at (48, 95)
render a tree of type TreeType.apple_tree and age 7 at (47, 34)
render a tree of type TreeType.apple_tree and age 21 at (82, 31)
render a tree of type TreeType.cherry_tree and age 5 at (20, 60)
render a tree of type TreeType.cherry_tree and age 22 at (25, 82)
render a tree of type TreeType.cherry_tree and age 16 at (28, 66)
render a tree of type TreeType.cherry_tree and age 11 at (21, 26)
render a tree of type TreeType.cherry_tree and age 9 at (37, 41)
render a tree of type TreeType.peach_tree and age 10 at (71, 57)
render a tree of type TreeType.peach_tree and age 9 at (47, 24)
render a tree of type TreeType.peach_tree and age 26 at (92, 32)
render a tree of type TreeType.peach_tree and age 29 at (75, 8)
render a tree of type TreeType.peach_tree and age 6 at (93, 50)
render a tree of type TreeType.peach_tree and age 21 at (54, 89)
render a tree of type TreeType.peach_tree and age 16 at (94, 18)
trees rendered: 15
trees actually created: 3
此處实现了支持三种不同的树家族,並通过render()方法提供隨機年龄和座標等資訊,特別注意的是仅成功地创建3个不同的对象而不是15个。
總结:
在应用開發上需要创建计算代价大但共享许多属性的大量对象时想要优化内存使用以提高应用性能时可使用享元模式來创建對象,這在内存受限之嵌入式系统或关注性能之图形软件和游戏中相当重要,但須掌握重点的是要将不可变共享的属性与可变的属性作区分。