一、享元模式定义:
享元模式是一种用于解决资源和性能压力时会使用到的设计模式,它的核心思想是通过引入数据共享来提升性能。
在开发3D游戏时,例如有成千上万的士兵或者有成千上万棵树,如果一个3D地带的每个对象都单独创建,不使用数据共享,那么性能是无法接受的。
享元设计模式就是通过为相似对象映入数据共享来最小化内存的使用,提升性能。
既然要创建成千上万个士兵,那么若他们的数据属性行为都是一样的,那岂不是黏一块去了。这时候就会有:可变数据和不可变数据的概念。
重点在于将不可变(可共享)的属性与可变的属性区分开。相同类型的对象共享不可变(可共享)的数据,而每个对象又有其独立的数据,这部分数据即为:可变的属性(不可共享数据)。
1.1、实现:
其实享元模式的实现与单例模式的实现方式十分相似,比如:单例模式实现的是一个类对象只允许有一个实例对象,而享元模式则是一个类对象只允许创建不同类型的对象,这样保证同一类型的对象共享不可变数据。
1.2、优点
相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
1.3、缺点
1.为了共享对象,需要将不能共享的状态外部化,会增加程序的复杂性
2.对享元模式的外部状态会增长运行时间
1.4、享元模式中存在的两种状态
1.内部状态,不会随着环境的改变而改变,可共享的部分
2.外部状态,会随着环境的改变而改变,是不可共享的部分.
享元模式就是要区分这两种状态,并将外部状态外部化。
二、应用实例
1.java中的String,如果有则返回,如果没有就创建一个字符串保存在字符串缓冲池里面。
2.数据库的数据池
2.1、应用场景
1.系统中存在大量的相似对象
2.细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
3.需要缓冲池的场景
三、享元模式的主要角色
抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
3.1、举例说明什么是具体享元角色和非具体享元角色
举下围棋的例子
棋子:是抽象享元角色
白子:具体享元角色
黑子:具体享元角色
围棋工厂:是享元工厂角色
下棋的位置:非具体享元角色
3.2、代码举例:
from enum import EnumTreeType = Enum('TreeType','apple_tree cherry_tree peach_tree')class Tree:pool = dict()def __new__(cls, tree_type, *args,**kwargs):obj = cls.pool.get(tree_type,None)if not obj:obj = super().__new__(cls,*args, **kwargs)cls.pool[tree_type] = objobj.tree_type = tree_typereturn objdef __init(self,size ):self.size = sizedef render(self,age,x,y):print('render a tree of type {} and age {} at ({},{})'.format(self.tree_type,age,x,y))
这样就实现了一个简单的享元模式:即通过其中的__new__魔法方法来限制类的实例化,只允许实例化不同类型的对象。
通过一个类型池,若需要实例化的类型在该类型池中,则直接返回该类型池中的对象,由于返回的是同一对象,故其共享不可变的属性(tree_type),而在执行完成__new__()方法之后,变化执行__init__魔法方法,则这时候该对象的属性便会发生改变,故不共享可变的属性(size)。
既然我们实现了一个简单的享元模式,那么怎么去使用它呢?
import random
from enum import Enumdef main():rnd = random.Random()age_min, age_max = 1, 30min_piont, max_point = 0, 100tree_counter = 0for _ in range(10):t1 = Tree(TreeType.apple_tree)t1.render(rnd.randint(age_min, age_max),rnd.randint(min_piont, max_point),rnd.randint(min_piont, max_point))tree_counter += 1for _ in range(3):t1 = Tree(TreeType.cherry_tree)t1.render(rnd.randint(age_min, age_max),rnd.randint(min_piont, max_point),rnd.randint(min_piont, max_point))tree_counter += 1for _ in range(5):t1 = Tree(TreeType.peach_tree)t1.render(rnd.randint(age_min, age_max),rnd.randint(min_piont, max_point),rnd.randint(min_piont, max_point))tree_counter += 1print(Tree.pool)if __name__ == '__main__':main()
在main()中去创建10棵apple_tree,并且 为每个对象随机给不同的年龄、位置等,这样就可以在游戏中的不同的位置中渲染。
输出结果为:
render a tree of type TreeType.apple_tree and age 17 at (48,57)
render a tree of type TreeType.apple_tree and age 30 at (27,9)
render a tree of type TreeType.apple_tree and age 4 at (74,92)
render a tree of type TreeType.apple_tree and age 16 at (8,95)
render a tree of type TreeType.apple_tree and age 26 at (43,95)
render a tree of type TreeType.apple_tree and age 1 at (80,20)
render a tree of type TreeType.apple_tree and age 26 at (21,88)
render a tree of type TreeType.apple_tree and age 22 at (53,57)
render a tree of type TreeType.apple_tree and age 17 at (65,47)
render a tree of type TreeType.apple_tree and age 24 at (34,77)
render a tree of type TreeType.cherry_tree and age 18 at (71,41)
render a tree of type TreeType.cherry_tree and age 30 at (63,33)
render a tree of type TreeType.cherry_tree and age 13 at (56,53)
render a tree of type TreeType.peach_tree and age 27 at (44,80)
render a tree of type TreeType.peach_tree and age 21 at (29,60)
render a tree of type TreeType.peach_tree and age 14 at (62,52)
render a tree of type TreeType.peach_tree and age 20 at (37,63)
render a tree of type TreeType.peach_tree and age 7 at (30,8)
{<TreeType.apple_tree: 1>: <__main__.Tree object at 0x00000253D1183AC8>,<TreeType.cherry_tree: 2>: <__main__.Tree object at 0x00000253D1187080>,
<TreeType.peach_tree: 3>: <__main__.Tree object at 0x00000253D1187978>}
其实可以发现同一类型的树对象,其ID均一样,而其size属性却不一样,这是由于在执行__init__方法时,返回类型池中的对象后,在进行初始化会size属性会覆盖前面返回的对象的size属性值。
总结:
该示例中,在__new__方法中实现类不可变数据的共享。
在__init__方法中实现了可变数据的独立,即不共享。