Contents
  1. 1. Structural Design Patterns
  2. 2. Adapter
    1. 2.1. What
    2. 2.2. Why
    3. 2.3. Solution
    4. 2.4. Consequences
  3. 3. Bridge
    1. 3.1. What
    2. 3.2. Why
    3. 3.3. Solution
    4. 3.4. Consequences
  4. 4. Composite
    1. 4.1. What
    2. 4.2. Why
    3. 4.3. Solution
    4. 4.4. Consequences
  5. 5. Decorator
    1. 5.1. What
    2. 5.2. Why
    3. 5.3. Solution
    4. 5.4. Consequences
  6. 6. Facade
    1. 6.1. What
    2. 6.2. Why
    3. 6.3. Solution
    4. 6.4. Consequences
  7. 7. Flyweight
    1. 7.1. What
    2. 7.2. Why
    3. 7.3. Solution
    4. 7.4. Consequences
  8. 8. Proxy
    1. 8.1. What
    2. 8.2. Why
    3. 8.3. Solution
    4. 8.4. Consequences
  9. 9. References

本篇将介绍设计模式中的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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public interface Target{
public void requestA();
}

public class TargetImpl{
public void requestA(){
System.out.println("requestA");
}
}

public interface Adaptee{
public void requestB();
}

public class AdapteeImpl implements Adaptee{
public void requestB(){
System.out.println("requestB");
}
}

public class Adapter implements Target{
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
public void requestA(){
System.out.println("Adapter requestA");
}
public void requestB(){
adaptee.requestB();
}
}

public class Client{
public static void main(String[] args){
Adapter adapter = new Adapter(new AdapteeImpl());
adapter.requestA();
adapter.requestB();
}
}

方法二:Class Adapter(Adapter 继承 AdapteeImpl 和实现 Target 接口)

Click to expand!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface Target{
public void requestA();
}

public class TargetImpl{
public void requestA(){
System.out.println("requestA");
}
}

public interface Adaptee{
public void requestB();
}
public class AdapteeImpl implements Adaptee{
public void requestB(){
System.out.println("requestB");
}
}
public class Adapter extends AdapteeImpl implements Target{
public void requestA(){
System.out.println("Adapter requestA");
}
}

public class Client{
public static void main(String[] args){
Adapter adapter = new Adapter();
adapter.requestA();
adapter.requestB();
}
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
abstract interface Abstraction{
protected Implementor implementor;
protected Abstraction(Implementor implementor){
this.implementor = implementor;
}
abstract public void operation();
}

public class AbstractionImpl{
public AbstractionImpl(Implementor implementor){
super(implementor);
}
public void operation(){
implementor.operationImpl();
}
}

public interface Implementor{
void operationImpl();
}

public class ImplementorImpl1{
public void operationImpl(){
System.out.println("operation implements by ImplementorImpl1...");
}
}
public class ImplementorImpl2{
public void operationImpl(){
System.out.println("operation implements by ImplementorImpl2...");
}
}

public class Client{
public static void main(String[] args){
Abstraction abstraction = new AbstractionImpl(new ImplementorImpl2());
abstraction.operation();
}
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public interface Component{
void operation();
void add(Component component);
void remove(Component component);
Component getChildren(int index);
}

public class Composite implements Component{
private String name;
private List<Composite> compositeList;

public Composite(String name){
this.name = name;
}
public void operation(){
System.out.println("I am " + name);
if (compositeList != null){
for (Composite composite : compositeList){
composite.operation();
}
}
}
public void add(Composite composite){
if (compositeList == null){
compositeList = new ArrayList<Composite>();
}
compositeList.add(Composite)
}
public void remove(Composite composite){...}
public Composite getChild(int index){
if (index >= 0 && && composite != null && index < compositeList.size()){
return compositeList.get(index);
}
return null;
}
}

public class Client{
public static void main(String[] args){
Component parent = new Composite("parent");
Component child = new Composite("child");
parent.add(child);
parent.operation();
child.operation();
}
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface Component{
pubilc String operation();
}

public class ConcreteComponet implements Component{
public String operation(){
return "concreteComponent operation..."
}
}

public interface Decorator extends Component{}

public class ConcreteDecorator implements Decorator{
private Component component;
public ConcreteDecorator(Component component){
this.component = component;
}
public String operation(){
return this.component.operation() + "ConcreteDecorator1 operation..."
}
}

public class Client{
public static void main(String[] args){
Component component = new ConcreteComponent();
component = new ConcreteDecorator(component);
System.out.println(component.operation());
}
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public interface SubSystemInterface{
public String handleRequest();
}
public class SubSystemClass1 implements SubSystemInterface{
public String handleRequest(){
return "SubSystemClass1 return result...";
}
}
public class SubSystemClass2 implements SubSystemInterface{
public String handleRequest(){
return "SubSystemClass2 return result...";
}
}

public class Facade{
public String handleRequest1(){
return new SubSystemClass1().handleRequest();
}
public String handleRequest2(){
return new SubSystemClass2().handleRequest();
}
}

public class Client{
public static void main(String[] args){
Facade facade = new Facade();
String result1 = facade.handleRequest1();
String result2 = facade.handleRequest2();
}
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface Flyweight{
void operation(String extrinsicState);
}

public class ConcreteFlyweight implements Flyweight{
public String intrinsicState;
public ConcreteFlyweight(String intrinsicState){
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState){
return intrinsicState;
}
}

public class FlyweightFactory{
Map<key, Flyweight> flyweightList = new HashMap<>();

public Flyweight getFlyweight(String key){
if (flyweightList.keySet().contains(key)){
return flyweightList.get(key);
}else{
Flyweight newFlyweight = new ConcreteFlyweight(key);
flyweightList.add(newFlyweight);
return newFlyweight;
}
}
}

public class Client{
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight flyweight = flyweightFactory.get("a");
flyweight.operation("print")
}

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!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface Subject{
void request();
}

public class RealSubject implements Subject{
public void request(){
System.out.println("request to RealSubject...");
}
}

public interface Proxy extends Subject{}

public class ConcreteProxy implements Proxy{
RealSubject realSubject = new RealSubject();
public void request(){
System.out.println("before ...");
realSubject.request();
System.out.println("after ...");
}
}

public class Client{
public static void main(String[] args){
Subject subject = new ConcreteProxy();
subject.request();
}
}

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

Contents
  1. 1. Structural Design Patterns
  2. 2. Adapter
    1. 2.1. What
    2. 2.2. Why
    3. 2.3. Solution
    4. 2.4. Consequences
  3. 3. Bridge
    1. 3.1. What
    2. 3.2. Why
    3. 3.3. Solution
    4. 3.4. Consequences
  4. 4. Composite
    1. 4.1. What
    2. 4.2. Why
    3. 4.3. Solution
    4. 4.4. Consequences
  5. 5. Decorator
    1. 5.1. What
    2. 5.2. Why
    3. 5.3. Solution
    4. 5.4. Consequences
  6. 6. Facade
    1. 6.1. What
    2. 6.2. Why
    3. 6.3. Solution
    4. 6.4. Consequences
  7. 7. Flyweight
    1. 7.1. What
    2. 7.2. Why
    3. 7.3. Solution
    4. 7.4. Consequences
  8. 8. Proxy
    1. 8.1. What
    2. 8.2. Why
    3. 8.3. Solution
    4. 8.4. Consequences
  9. 9. References