「HARD」4.6-Flyweight-享元-结构型模式

用途

运用共享技术支持大量细粒度的对象节约空间

示例

面向对象中创建一个对象的开销是相对较大的,有时这是难以承受的。例如文本编辑器若将每个字符均采用一个相应的对象描述将产生巨大的内存负担

Flyweight 模式描述了如何共享对象,使得可以细粒度地使用它们而不需要高昂的代价

  • 说明:flyweight 是一个共享对象,它可以同时在多个场景/上下文(context)中使用,并且在每个场景中 flyweight 都可以作为一个独立的对象
    • 关于内部与外部状态:
      • flyweight 存储独立于上下文的信息,在每个场景中使用
      • 有关上下文的外部状态由用户在合适的时候提供
  • Flyweight 模式对那些通常由于数量太大而难以用对象来表示的概念或实体进行建模
    • 例如文本编辑器中:
      • 逻辑上每个字符对应一个对象
      • 物理上每种字符共享同一个 Flyweight 对象(存储在 Flyweight 对象池),只存储字符编码,不存储相应的图元位置和字体等信息
      • 行对象绘制字符对象时传递位置、字体等信息

适用性

条件全部成立时使用:

  • 一个应用程序使用了大量的对象,完全由于使用大量的对象造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识(共享对象 a equal b 会返回真值)。

结构

  • FlyweightFactory:创建、管理、共享 Flyweight 对象。(采用懒加载创建)
  • Flyweight(示例中的 Glyph):描述接口,通过这个接口 flyweight 可以接受并作用于外部状态。
  • ConcreteFlyweight(示例中的 Character):(该对象必须是可共享的)实现 Flyweight 接口,并为内部状态(如果有的话)分配存储空间。
  • UnsharedConcreteFlyweight(示例中的 Row、Column):并非所有的 Flyweight 子类都需要被共享
  • Client
    • 引用 Flyweight
    • 计算/存储 Flyweight 的外部状态
    • 用户不应直接对 ConcreteFlyweight 类进行实例化,而只能从 FlyweightFactory 对象得到 ConcreteFlyweight 对象,这可以保证对它们适当地进行共享。

优缺点

  • 增大时间开销:传输、查找和/或计算外部状态都会产生运行时开销
  • 减少空间开销:
    • 共享的 flyweight 越多,存储节约也就越多。
    • 节约量随着共享状态的增多而增大。
    • 当对象使用大量的内部及外部状态,并且外部状态是计算出来的而非存储的时候,节约量将达到最大。

Flyweight 模式经常和 Composite(4.3)模式结合起来表示一个层次式结构,这一层次式结构是一个共享叶结点的图。

  • 由于共享,叶结点(Flyweight 对象)不能存储指向父结点的指针

实现

删除外部状态

该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。

  • 如果不同种类的外部状态和共享前对象的数目相同的话,删除外部状态不会降低存储消耗。
  • 理想的状况是,外部状态可以由一个单独的对象结构计算得到,且该结构的存储要求非常小。

管理共享对象

  • FlyweightFactory 对象经常使用关联存储帮助用户查找感兴趣的 flyweight 对象。
  • 共享还意味着某种形式的引用计数和垃圾回收,这样当一个 flyweight 不再使用时,可以回收它的存储空间。
  • 当 flyweight 的数目固定而且很小的时候(例如,用于 ACSII 码的 flyweight),这两种操作都不必要。在这种情况下,flyweight 完全可以永久保存。

相关模式

  • Flyweight 模式通常和 Composite(4.3)模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。
  • 通常,最好用 flyweight 实现 State(5.8)和 Strategy(5.9)对象。