9.1 Bridge 模式
Bridge 的意思是 “桥梁”。就像在现实中,桥梁的功能是将河流的两侧连接起来一样,Bridge 模式的作用也是将两样东西连接起来,它们分别是 类的功能层次结构 和 类的实现层次结构。
Bridge 模式的作用是在 “类的功能层次结构” 和 “类的实现层次结构” 之间搭建桥梁。
|| 类的层次结构的两个作用
◆ 希望增加新功能时
假设现在有一个类 Something。当我们想在 Something 中增加新功能时(具体的方法),会编写一个 Something 类的子类(派生类),即 SomethingGood 类,这样就构成了一个小小的类层次结构。
这就是为了增加新功能而产生的层次结构。
- 父类具有基本功能
- 在子类中增加新的功能
以上这种层次结构被称为 “类的功能层次结构”。
如果我们要继续在 SomethingGood 类的基础上增加新的功能,这是,我们可以同样地编写一个 SomethingGood 类的子类,即 SomethingBetter 类。
当要增加新的功能时,我们可从各个层次的类中找出最符合自己需求的类,然后以它为父类编写子类,并在子类中增加新的功能。这就是 “类的功能层次结构”。
注意:通常来说,类的功能层次结构关系不应当过深。
◆ 希望增加新的实现时
在 Template Method 模式中,学习了抽象类的作用。抽象类声明了一些抽象方法,定义了接口,然后子类负责去实现这些抽象方法。父类的任务是通过声明抽象方法的方式定义接口,而子类的任务是实现抽象方法。正是由于父类和子类的这种任务分担,我们才可以编写出具有高可替换性的类。
这里也存在层次结构。例如,当子类 ConcreteClass 实现了父类 AbstractClass 类的抽象方法时。
但是,这里的类的层次结构并非用于增加功能,也就是说,这种层次结构并非用于方便我们增加新的方法,他的真正作用是帮助我们实现下面这样的任务分担。
- 父类通过声明抽象方法来定义结构
- 子类通过实现具体的方法来实现接口
这种层次结构被称为 “类的实现层次结构”。
当我们以其他方式实现 AbstractClass 时,例如要实现一个 AnotherConcreteClass 时,类的层次结构会稍微发生一些变化。
为了一种新的实现方式,我们继承了 AbstractClass 子类,并实现了其中的抽象方法,这就是类的实现层次结构。
◆ 类的层次结构的混杂与分离
当我们想要编写子类时,就需要像这样确认自己的意图:“我是要增加功能呢?还是要增加实现呢?” 当类的层次结构只有一层时,功能层次结构与实现层次机构是混杂在一个层次结构中的。这样很容易使类的层次结构变得复杂,难以透彻地理解类的层次结构。 我们很难确定究竟应该在类的哪一个层次结构中去增加子类。
因此,我们需要将 “类的功能层次结构” 与 “类的实现层次结构” 分离为两个独立的类层次结构。本章要学习的 Bridge 模式就是在这两者之间搭建桥梁,使这两者之间联系在一起。
9.2 示例程序
作用:显示一些东西。
|| 类的功能层次结构:Display 类
Display 类的功能是抽象的,负责 “显示一些东西”。该类位于 “类的功能层次结构” 的最上层。
在 impl 字段中保存的是实现了 Display 类的具体功能的实例。该实例通过 Display 类的构造方法被传递给 Display 类,然后保存到 impl 字段中,以供后面的处理使用(impl 字段即两个层次结构的 “桥梁”)。
注意类中的 3 个方法的实现,都调用了 impl 字段的实现方法。这样 Display 接口就被转换成了 DisplayImpl 的接口。
/**
* 负责显示的类
*
*/
public class Display {
private DisplayImpl displayImpl;
public Display(DisplayImpl display) {
this.displayImpl = display;
}
public void open() {
displayImpl.rawOpen();
}
public void print() {
displayImpl.rawPrint();
}
public void close() {
displayImpl.rawClose();
}
public final void display() {
open();
print();
close();
}
}
|| 类的功能层次结构:CountDisplay 类
CountDisplay 类在 Display 类的基础上增加了一个功能。Display 类只具有显示的功能,而 CountDisplay 类则具有 “只显示规定的次数” 的功能,这就是 multiDisplay 方法。
CountDisplay 类继承了 Display 类的 open、print、close 方法,并使用它们来增加这个新功能。这就是 “类的功能层次结构”。
/**
* 类的功能层次结构: CountDisplay 类
* 增加了 只显示规定次数 功能
*/
public class CountDisplay extends Display{
public CountDisplay(DisplayImpl display) {
super(display);
}
public void multiDisplay(int times) {
open();
// 循环打印 times 次
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
|| 类的实现层次结构:DisplayImpl 类
桥的另一侧 - “类的实现层次结构”
DisplayImpl 类位于 “类的实现层次结构” 的最上层。
DisplayImpl 类是抽象类,声明了 rawOpen、rawPrint、rawClose 这3个抽象方法。
/**
* 类的实现层次结构:DisplayImpl 类
*/
public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
|| 类的实现层次结构:StringDisplayImpl 类
真正的实现, StringDisplayImpl 类是显示字符串的类。他继承了 DisplayImpl 类,作为其子类来使用 3 个抽象方法进行显示。
/**
* 类的实现层次结构,继承DisplayImpl,作为其子类来使用
*/
public class StringDisplayImpl extends DisplayImpl{
private String str;
private int width;
public StringDisplayImpl(String str) {
this.str = str;
this.width = str.getBytes().length;
}
@Override
public void rawOpen() {
printLine();
}
@Override
public void rawPrint() {
System.out.println("|" + str + "|");
}
@Override
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
DisplayImpl、StringDisplayImpl 这两个类相当于 “类的实现层次结构”。
|| Main 类
将上述 4 个类组合起来显示字符串。
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);
}
}
运行结果:
+-------------+
|Hello, China.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
9.3 Bridge 模式中的登场角色
◆ Abstraction (抽象化)
该角色位于 “类的功能层次结构” 的最上层。它使用 Implementor 角色的方法定义了基本的功能。该角色中保存了 Implemetor 角色的实例。在实例程序中,由 Display 类扮演此角色。
◆ RefinedAbstraction(改善后的抽象化)
在 Abstraction 角色的基础上增加了新功能的角色。在示例程序中,由 CountDisplay 类扮演此角色。
◆ Implementor (实现者)
该角色位于 “类的实现层次结构” 最上层。它定义了用于实现 Abstaction 角色的接口的方法。在示例程序中,由 DisplayImpl 类扮演此角色。
◆ Abstraction (具体实现者)
该角色负责实现在 Implementor 角色中定义的接口。在示例程序中,由 StringDisplayImpl 类扮演此角色。
Bridge 模式的类图如下所示。左侧的两个类构成了 “类的功能层次结构”,右侧两个类构成了 “类的实现层次结构”。类的两个层次结构之间的桥梁是 impl 字段。
9.4 拓展思路的要点
|| 分开后更容易扩展
Bridge 模式的特征是将 “类的功能层次结构” 与 “类的实现层次结构” 分离开了。将类的这两个层次结构分离开有利于独立地对它们进行扩展。
当想要增加功能时,只需要在 “类的功能层次结构” 一侧增加类即可,不必对 “类的实现层次结构” 做任何修改。而且,增加后的功能可以被 “所有的实现” 使用。
例如,可以将 “类的功能层次结构” 应用于软件所运行的操作系统上。如果我们将某个程序中依赖于操作系统的部分划分为 Windons 、Mac、Unix 版,那么我们可以用 Bridge 模式中的 “类的实现层次结构” 来表现这些依赖于操作系统的部分。我们需要编写一个定义这些操作系统的共同接口的 Implementor 角色,然后编写 Windows、Mac、Unix 版的 3 个 ConcreteImplementor 角色。这样一来,无论在 “类的功能层次结构” 中增加多少功能,它们都可以工作于这3个操作系统上。
|| 继承是强关联,委托是弱关联
虽然继承很容易扩展类,但是类之间也形成了一种强关联关系。只有通过修改代码,才可以改变这种关系。
如果想要很轻松地改变类之间的关系,我们可以使用 “委托” 来代替 “继承” 关系。示例程序中 Display 类中使用了 “委托”。Display 类的 impl 字段保存了实现的实例,使得类的任务发生了转移。Display 类并非自己工作,而是将工作 “交给 impl”。