【设计模式】享元模式与策略模式
本文最后更新于:2023年12月17日 下午
Flyweight(享元)模式
1. 意图
运用共享技术有效地支持大量细粒度的对象
2. 动机
Flyweight模式描述了如何共享对象,使得可以细粒度地使用它们而不需要高昂的代价。
Flyweight是一个共享对象,他可以同时在多个场景(context)中使用,并且在每个场景中flyweight都可以作为一个独立的对象——这一点与非共享对象的实例没有区别。flyweight不能对它所运行的场景做出任何假设,这里关键的概念是内部状态与外部状态之间的区别。内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。
Flyweight模式对那些通常由于数量太大而难以用对象来表示的概念或实体进行建模。
3. 适用性
Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当一下情况成立时,使用Flyweight模式:
- 一个应用使用了大量的对象。
- 完全由于使用大量的对象造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很对组对象
- 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,因此概念上明显有别的对象,标识测试将返回真值。
4. 结构
下面对象图说明了如何共享flyweight
5. 参与者
- Flyweight(Glyph)
- 描述一个接口,通过这个接口flyweght可以接受并作用于外部状态
- ConcreteFlyweight(character)
- 实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的。即它必须独立于ConcreteFlyweight 对象的场景。
- UnsharedConcreteFlyweight(Row、Column)
- 并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
- FlyweightFactory
- 创建并管理Flyweight对象
- 确保合理地共享Flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个(如果不存在的话)
- Client
- 维持一个对Flyweight的引用
- 计算或存储一个(或多个)flyweight的外部状态
6. 协作
- flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象中,而外部状态则是由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。
- 用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象。这可以保证对它们适当地进行共享。
7. 实现
在实现Flyweight模式时,应注意一下几点:
① 删除外部状态。该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。
② 管理共享对象。 因为对象是共享的,用户不能直接对它进行实例化,所以FlyweightFactory来帮助用户查找某个特定的flyweight对象。共享还意味着某种形式的引用计数和垃圾回收,这样当一个flyweight不再使用时,可以回收它的存储空间。
8. 示例代码
1 |
|
1 |
|
9. 相关模式
Flyweight模式通常和Composite(组合)模式结合起来,用共享叶节点的有向无环图实现一个逻辑上的层次结构。
通常,最好用flyweight实现State和Strategy对象。
Strategy(策略)模式
1. 意图
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化
2. 别名
政策(policy)
3. 适用性
在以下情况下使用Strategy模式:
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
4. 结构
5. 参与者
- Strategy(策略,如Compositor)
- 定义所支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法
- ConcreteStrategy(具体策略,如SimpleCompositor、TeXCompositor、ArrayCompositor)
- 以Strategy接口实现具体算法
- Context(上下文,如Composition)
- 用一个ConcreteStrategy对象来配置
- 维护一个Strategy对象的引用
- 可定义一个接口来让Strategy访问它的数据
6. 协作
- Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context
- Context将客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context,这样客户仅与Context交互。通常有一系列Strategy类可供客户从中选择。
7. 实现
考虑下面的实现问题:
- 定义Strategy和Context接口。
- Strategy和Context接口必须使得ConcreteStrategy能够有效地访问它所需要的context中的任何数据,反之亦然。一种方法是让Context将数据放在参数中传递给Strategy,这使得Strategy和Context解耦,但Context也可能发送一些Strategy不需要的数据。
- 另一种方法是让Context将自身作为一个参数传递给Strategy,该Strategy再显式地想Context请求数据。或者Strategy可以存储对它的Context的引用。
- 将Strategy作为模板参数
- 在C++中,可利用模板机制用一个Strategy来配置一个类。然而这种方法仅当满足下面条件时才能使用:① 可以在编译时选择Strategy;② 不需要在运行时改变。在这种情况下,要被配置的类(如Context)被定义为一个以Strategy类作为参数的模板类。
- 使Strategy对象成为可选的
- 即使在不使用额外的Strategy对象的情况下,Context也有意义的话,那么它还可以被简化。Context在访问某Strategy前先检查它是否存在。如果有,就使用它;如果没有,那么Context执行缺省的行为。这种方法的好处是客户根本不需要处理Strategy对象,除非不喜欢缺省的行为。
8. 示例代码
1 |
|
1 |
|
9. 相关模式
Flyweight:Strategy对象通常是很好的轻量级对象