深入理解设计模式(三):结构型模式
本篇将介绍设计模式中的7种结构型模式,其中设计模式的实现代码使用 Java 语言描述。
Structural Design Patterns
结构型设计模式关注 classes 和 objects 如何组成更大的结构。
Adapter
What
Adapter 将一个 interface class 转换为 Client 期望的另一个 interface。Adapter 让不兼容的 classes 一起工作。
Why
Motivation
一个不能兼容另一个接口的接口,希望他能够兼容。
例子:画图编辑器可以让用户画图和布置图形元素,如 lines,polygons,text 等。定义了一个抽象的图形化接口为 Shape。为每种图形对象定义子类,LineShape 对应 lines,PolygonShape 对应 polygons。基本的集合图形是比较容易实现的,但是对文本的编辑很难实现,文本类 TextView 存在额外的方法,它不能兼容 Shape 接口。我们可以使用 Adapter 模式让其兼容。我们可以定义 TextShape 它使得 TextView 接口适应 Shape 接口,其中 TextShape 为 Adapter,TextView 为 Adaptee。
Adapter 模式有两种实现方式,一种是继承,另一种是组合。(1)让 TextShape 实现 Shape 接口,以及继承 TextView 的实现。(2)让 TextShape 实现 Shape 接口,以及组合一个 TextView 实例。
Applicability
- 你想要用一个已存在的 class,它的接口不匹配你的需求。
- 你想要创建一个重用的 class ,它能够于不相关的或无法预见的 classes 合作,即不一定具有兼容的接口。
- 你需要使用一些存在的子类,但是它是不能兼容每一个子类的接口。
Solution
Structure
Adapter 使用 multiple inheritance 实现的结构:
Adapter 使用 object composition 实现的结构:
Participants
- Target:定义 Client 使用的特定领域的接口。
- Client:与符合 Target 接口的对象协作。
- Adaptee:定义一个需要适应的接口。
- Adapter:使 Adaptee 接口适应 Target 接口的类。
Collaborations
- Clients 请求 Adapter 实例的方法。Adapter 通过请求 Adaptee 的方法得到结果。
- Adapter 让 Adaptee 适应 Target。
Implementations
方法一:Object Adapter(Adapter 组合 Adaptee 实例和实现 Target 接口)
Click to expand!
public interface Target{ |
方法二:Class Adapter(Adapter 继承 AdapteeImpl 和实现 Target 接口)
Click to expand!
public interface Target{ |
Consequences
Class Adapter (Inheritance)
- 它只能让 Adaptee 的具体子类适应 Target。但它不能使一个 class 和它的子类都适应 Target。
- 可以让 Adapter 重写 Adaptee 的一些行为。
- 仅仅引入一个对象,并且不需要其他指针间接访问 adaptee。
Object Adapter (Composition)
- Adapter 让 Adaptee 和它的所有子类都适应 Target。Adapter 可以同时为所有 Adaptee 增加功能。
- 很难 override Adaptee 的行为。他需要参考 Adaptee 的子类,而不是自己作为它的子类。
Bridge
What
Bridge 模式解耦抽象与它的实现,因此它们可以独立地改变。抽象类和实现类没有通过 implement 关键字进行绑定,而是通过构造方法注入的方式,将实现类注入到抽象类中。
Why
Motivation
一个抽象有很多种可能的实现,通常使用 inheritance 来实现。定义一个 interface 去抽象,和具体的 subclasses 不同的方式去实现。但是这种方式不总是足够的灵活。inheritance 将 implementation 永久地绑定了 abstraction,这使得很难独立地去修改、扩展,以及重用 abstraction 和 implementation。
例子:Window 需要在两个平台(X Window System and IBM’s Presentation Manager)实现,可以定义一个抽象类 Window 和定义两个子类 XWindow 和 PMWindow。这个方法有两个缺点:1. 它不方便去扩展 Window 抽象在不同种类的 windows 或新的平台。支持新的 IconWindow 需要实现两个新的类 XIconWindow 和 PMIconWindow。支持新的平台需要每种 window 都需要新的 Window 子类。2. 它使得 client 代码是平台依赖的。
Client 应该能够创建 window 对象时不需要指定具体的实现。仅仅只有 window implementation 应该是依赖平台的,Windows 的 abstraction 不需要指定平台。
Applicability
- 你想避免永久地绑定抽象和它的实现。
- 抽象和它的实现都应该通过子类扩展。
- 改变一个抽象的实现应该不会对 Client 有影响。
- 你想要完全地隐藏一个抽象的实现。
- 你的 classes 种类非常多。class 层级结构表明 classes 需要分离到两个部分。
- 你想多个 objects 共享一个实现,并且这个事实应该对 client 隐藏。
Solution
Structure
Participants
- Abstraction:定义 abstraction 的接口。维护一个对 Implementor 的对象的参考。
- RefinedAbstraction:扩展 Abstraction 接口。
- Implementor:定义 implementation 的接口。
- ConcreteImplementor:实现 implementor 接口。
Collaborations
- Abstraction 将 client 的请求转发到它的 implementor 对象。
Implementations
Click to expand!
abstract interface Abstraction{ |
Consequences
Benefits
- 解耦 interface 和 implementation。
- 提升扩展性。你可以独立地扩展 Abstraction 和 Implementor 层级结构。
- 可以对 client 隐藏 implementation 的实现。
Composite
What
将对象组成树形结构以表示部分-整体层次结构。Composite 让 client 统一地对待单一对象和组合对象。
Why
Motivation
可以将多个 components 组成更大的 components,每个节点可以是单个组件,也可以是多个组件的组合,每个节点用统一的接口对象表示。
例子:如画图编辑器,可以画线、多边形和文字,也可以是图文的方式。图文这种元素是多种组件的结合,我们想把组合元素和单一元素统一对待,减少代码的复杂性。我么可以使用 Composite 模式来实现这个功能。
Applicability
- 你想表示对象的部分整体层次结构。
- 你想让 Client 忽略组合对象和单一对象的区别。Client 统一地对待所有 Composite 接口的 Object。
Solution
Structure
Participants
- Component:为 Composite 对象定义一个接口。为访问和管理子节点声明接口。
- Composite:实现 Component 的类。它为有 Children 的组件定义行为。存储子组件。实现 child-related 操作。
- Client:通过 Component 接口库操作 Composite 中的对象。
Collaborations
- Client 使用 Component class interface 去与 Composite 结构中的 Objects 进行交互。
Implementations
Click to expand!
public interface Component{ |
Consequences
Benefits
- 定义由 primitive objects 和 composite objects 组成的 class 层级结构。Primitive objects 可以组合为更复杂的 objects。
- 它使得 client 简单。 Client 可以统一地对待 composite structures 和 individual objects。
- 更容易添加新类型的 components。
Drawbacks
- 它使你的设计过于笼统。很难去限制 composite component。你需要自己在运行时检查对 composite component 的约束,如限制一个组合组件的子组件的个数。
Decorator
What
动态地给一个 object 附加额外的职责。Decorator 为子类提供了灵活的替代方案,以扩展功能。
Why
Motivation
有时我们想为 objects 添加职责,而不是整个 class。如,为文本阅读器添加 border 或者 scroll 等。
一种实现方式是通过 inheritance 来添加职责,这种方式是不灵活的,它是静态的,它为每一个实例都添加了这个职责,而且每次添加额外的职责都需要修改 class。另一种更灵活的方法是使用 Decorator 设计模式。它让你循环嵌套 decotrators,允许无限的添加职责。
Applicability
- 动态地、透明地为单个 object 添加职责,没有影响其他的 objects。
- 职责是可以被撤回的。
- 当不能使用子类去扩展。
Solution
Structure
Participants
- Component:定义组件接口。
- ConcreteComponent:实现组件接口的 class。
- Decorator:为 Component 对象维护一个引用。定义一个符合 Component 接口的接口。
- ConcreteDecorator:为 component 对象添加职责的 class。
Collaborations
- Decorator 将请求转发给它的 Component 对象。它可能选择性地在请求之前和之后执行额外的操作。
Implementations
Click to expand!
public interface Component{ |
Consequences
Benefits
- Decorator 比静态 inheritance 更灵活。
- 避免功能丰富的 classes 增加层级结构。
Drawbacks
- decorator 和它的 component 不是同一个 object。当你使用 decorator 时,你不应该依赖一个对象。
- 系统存在大量很小的 objects。
Facade
What
为一个 subsystem 中的一组接口提供一个统一的接口。Facade 定义了一个更高层级的接口,它使得 subsystem 更容易使用。
Why
Motivation
将系统构建为子系统有助于降低复杂性。一个普遍的设计目标时最小化系统之间的交流和依赖。一种实现这个目标的方式是使用 Facade 模式。
Applicability
- 你想为复杂的子系统提供一个简单的接口。
- 在 Client 和抽象类的实现之间有很多依赖。使用 Facade 去解耦子系统与 client 和其他子系统,从而提升子系统的独立性和可移植性。
- 你想将你的子系统分层。使用 Facade 为每个子系统定义一个接入点。
Solution
Structure
Participants
- Facade:知道 subsystem 中的哪个 class 能处理哪个 request。将 client 的请求委托给合适的 subsystem 的 objects。
- subsystem classes:subsystem 的 classes。
Collaborations
Implementations
Click to expand!
public interface SubSystemInterface{ |
Consequences
Benefits
- 它对 client 隐蔽子系统的 components,减少 client 需要处理的 objects 的数量,以及使得 subsystem 更容易使用。
- 它减少了 subsystem 和 clients 之间的耦合。
- 它没有阻止应用程序在需要时使用子系统的 classes。因此你可以在易用性和通用性之间进行选择。
Flyweight
What
通过共享可以有效地支持大量细粒度的对象。
Why
Motivation
一个应用存在大量的相同的元素,如果每个元素都使用一个对象去表示,那么会出现大量内容相似的对象,这样是没有必要的,造成空间的浪费。我们可以通过共享对象来处理这个问题。
例子:文档编辑器由文本编辑和格式化的功能。一个文档中如果每一个 character 创建一个对象,那么会出现大量的对象。我们可以每一种字符创建一个对象,然后所有相同类型的字符都共享一个相同的对象。
Applicability
- 应用使用了大量的对象。
- 对象的存储花费很高。
- 大部分对象是没有本质区别的,是可以共享相同对象的。
- 应用不依赖对象的本身。
Solution
Structure
Participants
- Flyweight:定义一个接口,Flyweight 可以通过该接口来接收外部状态,并对其存储的内部状态进行操作。
- ConcreteFlyweight:实现 Flyweight 接口的类。它存储 intrinsic state。它的对象是可被分享的。
- UnsharedConcreteFlyweight:不需要分享的 Flyweight 的 subclasses。不是所有的 Flyweight subclasses 需要被分享。
- FlyweightFactory:创建和管理 Flyweight 对象。确保 Flyweight 是正确地分享的。
- Client:维护对 Flyweight 的引用。计算和存储 Flyweight 的 extrinsic state。
Collaborations
- Intrinsic state 在 ConcreteFlyweight 对象中存储,Extrinsic state 在 Client 对象中存储和计算。当调用对象的操作时,Client 传递这个 state 给 flyweight。
- Client 不应该直接实例化 ConcreteFlyweight。Client 必须通过 FlyweightFactory 对象来获取 ConcreteFlyweight 对象,以确保它们是正确地分享。
Implementations
Click to expand!
public interface Flyweight{ |
Consequences
Benefits
- Flyweight 是节省空间的,它减少了对象实例的数量,对象被共享的越多节省越多的空间。
Drawbacks
- Flyweight 可能带来与 transferring, finding, computing extrinsic state 相关的 run-time costs。
Proxy
What
Proxy 为另一个对象提供了一个代孕(Surrogate)或占位符(Placeholder)去控制对这个对象的访问。
Why
Motivation
控制访问一个对象的原因是推迟它的创建和初始化的全部花费,直到我们真正需要使用它。如,文档编辑器可以在文档中嵌入图形对象。创建一些非常大的图像是十分昂贵的。一般来说,打开一个文档应该越快越好,所以我们应该避免在文档打开的时候立刻创建昂贵的对象。解决这个问题可以使用另一个 image proxy 对象,而不是 image 对象,proxy 对象作为真实 image 的替身。
Applicability
- Remote proxy。为在不同地址空间的对象提供一个本地的代表。
- Virtual proxy。创建昂贵的对象 on demand。
- Protection proxy。通过控制访问来保护原始对象。
- Smart reference。在访问对象时执行额外的操作。
Solution
Structure
运行时 proxy 结构
Participants
- Proxy:1)维护一个引用,让 proxy 访问 real subject。2)提供一个与 Subject 相同的接口,使得 proxy 可以代替 real subject。3)控制对 real subject 的访问,以及可能有创建和删除它的职责。
- Subject:为 RealSubject 定义一个公共接口。
- RealSubject:定义 proxy 表示的 real object。
Collaborations
- Proxy 在适当的时候将请求转发给 RealSubject,具体取决于 proxy 的类型。
Implementations
Click to expand!
public interface Subject{ |
Consequences
Benefits
- remote proxy 可以隐藏位于不同地址空间的对象。
- virtual proxy 可以执行优化。如,按需创建对象。
- protection proxy 和 smart reference 在对象被访问的时候允许额外的看管。
References
[1] Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides
[2] Difference between object adapter pattern and class adapter pattern