Bridge

在现实世界中,桥梁的功能是将河流的两侧连接起来。Bridge 模式的作用也是将两样东西连接起来,它们分别是『类的功能层次结构』和『类的实现层次结构』。那么,『类的功能层次结构』和『类的实现层次结构』分别指的是什么呢?

类的层次结构

在开始阅读 Bridge 模式的示例代码之前,我们需要先来理解一下这两种层次结构。这是因为如果不能理解河流两边的土地,也就无法理解桥梁存在的意义了。

划分

增加新的功能

假设有一个类 Something。当我们想在 Something 中增加新功能时(想增加一个具体的方法),会编写一个 Something 类的子类,即 SomethingGood 类。这样就构成了一个小小的层次结构。

1
2
Something
SomethingGood

这就是为了增加新的功能而产生的层次结构。

  • 父类具有基础功能
  • 在子类中增加新的功能

以上这种层次结构被称为『类的功能层次结构』。

增加新的实现

Template Method 模式中,我们学习了抽象类的作用。抽象类声明了一些抽象方法,定义好 API,然后由子类去负责实现这些抽象方法。

这里其实也存在层次结构。例如,当子类 ConcreteClass 实现了父类 AbstractClass 类的抽象方法时,他们之间就构成了一个小小的层次结构。

1
2
AbstractClass
concreteClass

但是,这里的类的层次结构并非用于增加功能。它的真正作用是帮助我们实现下面这样的任务分担。

  • 父类通过声明抽象方法来定义接口
  • 子类通过实现具体方法来实现接口

这种层次结构被称为『类的实现层次结构』。

作用

通过前面的学习,大家应该理解了类的功能层次结构与类的实现层次结构。那么,当我们想要编写子类时,就需要像这样先确认自己的意图:我是要增加功能呢?还是要增加实现呢?

示例程序

下面我们来看一段使用了 Bridge 模式的示例程序。这段程序的功能是『显示一些东西』。

在桥的哪一侧 类名 说明
功能层次 Display 负责『显示』的类
功能层次 CountDisplay 增加了『指定显示次数』功能
实现层次 DisplayImpl 负责『显示』的类
实现层次 StringDisplayImpl 『用字符串显示』的类
Main 测试程序行为

类结构图

功能_Display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Display {
private DisplayImpl impl;

public Display(DisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public final void display() {
open();
print();
close();
}
}

Display 类的功能是抽象的,负责『显示一些东西』。该类位于『类的功能层次结构』的最上层。

  • impl 字段中保存的是实现了 Display 类的具体功能的实例。impl 字段即是类的两个层次结构之间的桥梁。
  • open、print、close 这三个方法是 Display 类提供的接口,他们表示显示的步骤。这三个方法都调用了 impl 字段的实现方法。这样,Display 的接口就被转换成为了 DisplayImpl 的接口。

功能_CountDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CountDisplay extends Display {
public CountDisplay(DisplayImpl impl) {
super(impl);
}

//循环显示 times 次。
public void multiDisplay(int times) {
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}

CountDisplay 类在 Display 类的基础上增加了一个新功能。Display 类只具有『显示』的功能,CountDisplay 类则具有『显示规定次数』的功能,这就是 multiDisplay 方法。

实现_DisplayImpl

1
2
3
4
5
public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}

DisplayImpl 类位于『类的实现层次结构』的最上层。DisplayImpl 是抽象类,它声明了 rawOpen、rawPrint、rawClose 这三个抽象方法,他们分别与 Display 类中的 open、print、close 方法相对应。

实现_StringDisplayImpl

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
public class StringDisplayImpl extends DisplayImpl {
private String string;
private int width;

public StringDisplayImpl(String s) {
this.string = s;
this.width = s.getBytes().length;
}
@Override
public void rawOpen() {
printLine();
}
@Override
public void rawPrint() {
System.out.println("|" + string + "|");
}
@Override
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}

StringDisplayImpl 是真正的实现类。它继承了 DisplayImpl 类,实现了 DisplayImpl 中定义的接口。

Main

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
Display d1 = new Display(new StringDisplayImpl("Hello, China."));
Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
d1.display();
d2.display();
d3.display();
d3.multiDisplay(5);
}
}

运行结果

Bridge 中的角色

Bridge 中的角色介绍

总结

分开后更容易扩展

Bridge 模式的特征是将『类的功能层次结构』与『类的实现层次结构』分离开了。将类的这两个层次结构分离开有利于独立地对它们进行扩展(见习题)。当想要增加功能时,只需要在『类的功能层次结构』一侧增加类即可,不必对『类的实现层次结构』做任何修改。而且,增加后的功能可以被所有的实现使用。

  • 例如,我们可以将『类的功能层次结构』应用于软件所运行的操作系统上。如果我们将某个程序中依赖于操作系统的部分划分为 Windows 版、Macintosh 版、Unix 版,那么我们就可以用 Bridge 模式中的『类的实现层次结构』来表现这些依赖于操作系统的部分。也就是说,我们需要编写一个定义这些操作系统的共同接口(API)的 Implementor 角色,然后编写 Windows 版、Macintosh 版、Unix 版的 3 个 ConcreteImplementor 角色。这样一来,无论在『类的功能层次结构』中增加多少个功能,它们都可以工作于这 3 个操作系统上。

习题

(1) 请在本章的示例程序中增加一个类,实现『显示字符串若干(随机)次』的功能。请注意此时应当扩展(继承)哪个类。
提示用于显示的方法是 void randomDisplay(int times),它的作用是将字符串随机显示 0-times 次。

(2) 请在本章的示例程序中增加一个类,实现『显示文本文件的内容』的功能。请注意此时应当扩展(继承)哪个类。

(3) 请在本章的示例程序中增加类,以实现下图的输出效果。

输出结果示例1

输出结果示例2

请思考我们是应当在『类的功能层次结构』中增加类呢?还是应当在『类的实现层次结构』中增加类呢?

  • 需要一种新的显示方式,那么应当是在『类的功能层次结构』中增加类吧?不过,又好像只要增加一个新的显示字符串的方法就可以了,那么还是在『类的实现层次结构』中增加类会更好吧?究竟应该如何在 Bridge 模式中增加这个类呢?

答案

github 地址

引用