18 Flyweight享元(结构型)
-
- 享元:
- 享元指:次最轻量级的拳击选手
- 享元模式以共享的方式高效地支持大量的细粒度对象
- 通过尽量共享实例来避免new出实例。(new不仅消耗内存,还会花费时间)
- 动机:
- 一些应用程序可以从在整个设计过程中使用对象中获益,但是一个简单的实现将会非常昂贵。
- 面向对象的文档编辑器通常使用对象来表示嵌入的元素,如表和图。
- 这种设计的缺点是成本高。即使是中等大小的文档也可能需要几十万个字符对象,这将消耗大量内存,并可能导致不可接受的运行时开销。
- 从逻辑上讲,文档中每出现一个给定字符就有一个对象:
- flyweight是一个可以同时在多个上下文中使用的共享对象。
- 某一特定字符对象的每一次出现都引用flyweight对象共享池中的相同实例:
- 什么时候用:
- 应用程序使用大量对象。
- 存储成本很高,因为数量(绝对数量)的对象,例如,汉字。
- 大多数对象状态可以变成extrinsic外部。
- 一旦外部状态被移除,许多组对象可能会被相对较少的共享对象所取代。
- 应用程序不依赖于对象标识。
- 结构:
- 享元:
-
- 参与者:
- Flyweight:声明一个接口,通过该接口,flyweights可以接收和作用于外部的状态。
- 参与者:
-
-
- ConcreteFlyweight:
- 实现Flyweight接口,并为内部状态(如果有的话)添加存储。
- 一个具体的flyweight对象必须是可共享的。
- 它存储的任何状态都必须是固有的;也就是说,它必须独立于ConcreteFlyweight对象的上下文
- UnsharedConcreteFlyweight
- 并非所有Flyweight子类都需要共享。
- Flyweight接口支持共享;它没有强制执行。
- FlyweightFactory:
- 创建和管理flyweight对象,并确保正确共享flyweight。
- 当客户端请求flyweight时,FlyweightFactory对象提供一个现有实例,或者创建一个实例(如果不存在的话)。
- Client:
- 维护对flyweight的引用。
- 计算或存储flyweight的外部状态。
- ConcreteFlyweight:
-
-
- 协作:
- 一个flyweight需要功能的状态必须被描述为内在的或外在的intrinsic or extrinsic。
- 内部状态存储在ConcreteFlyweight对象中;
- 外部状态由客户端对象存储或计算。
- 一个flyweight需要功能的状态必须被描述为内在的或外在的intrinsic or extrinsic。
- 协作:
客户端在调用flyweight的操作时将此状态传递给该flyweight。
-
- 客户端不应该实例化
-
- 后果:
- Flyweights可能引入与传输、查找和/或计算外部状态相关的运行时成本。
- 存储节省是几个因素的函数:
- 实例总数的减少来自于共享
- 每个对象的内部状态的数量
- 无论外部状态是计算还是存储
- 共享的flyweights越多,存储节省就越大。
- 最大的节省发生在外部状态可以计算而不是存储的时候。然后您可以通过两种方式节省存储:共享可以降低内部状态的成本,并且可以用外部状态交换计算时间。
- 实现问题1:移除外部状态
- 模式的适用性在很大程度上取决于识别外部状态并将其从共享对象中移除的容易程度。
- 去除外部状态无助于降低存储成本。
- 理想情况下,外部状态可以从一个单独的对象结构中计算出来,这个对象结构的存储需求要小得多。
- (最好)客户端通过参数将外部状态存储并传递给flyweight。
- (更好)将外部状态和相关行为(代码)从flyweight一起删除到客户端。
- 封装外部状态和相应的行为(代码)来构建新的类。
- 实现问题2:处理共享对象
- 因为对象是共享的,所以客户端不应该直接实例化它们。FlyweightFactory允许客户定位特定的flyweight。
- FlyweightFactory对象通常使用关联存储来让客户端查找感兴趣的flyweights。管理器根据代码返回适当的flyweight,如果flyweight还不存在,就创建它。
- 可共享性还意味着某种形式的引用计数或垃圾收集,以便在不再需要flyweight存储时回收它。然而,如果享元的数量固定且较小,则这两种方法都不是必要的。
- 后果: