Bridge 桥接模式
Bridge 也就是桥梁,就像现实世界中,将河流的两侧连接起来一样,桥接模式的作用也是将两样东西连接起来,只不过它连接的是 类的功能层次结构和类的实现层次结构。 首先我们来理解一样这两种层次结构。
类的层次结构
类的功能层次结构 - 增加新的功能
假设现在有一个类 Something,当我们想在 Something 类中增加新的功能时(也就是增加一个方法),通常会编写一个 Something 的子类,如 SomethingGood 类,这样就构成了一个简单的类层次结构。
classDiagram
Something <|-- SomethingGood
这就是为了增加新的功能而产生的类的层次结构。父类有基本的功能,子类中新增新的功能。 如果我们要继续再 SomethingGood 类的基础上增加新的功能,可以同样编写一个 SomethingGood 的子类 SomethingBetter 类,这样,类的层次结构就又加深了一层。
classDiagram
Something <|-- SomethingGood
SomethingGood<|-- SomethingBetter
当要增加新的功能时,我们可以从各个层次的类中找出最符合自己需求的类,然后以其为父类编写子类,并在子类中增加新的功能,这就是 类的功能层次结构。
类的实现层次结构 - 增加新的实现
在开发时,通常我们会定义一些抽象类用来声明一些抽象方法,定义接口 API,然后子类负责去实现这些抽象方法。父类的任务是通过声明抽象方法的方式定义接口,子类的任务就是实现抽象方法,正因为父类和子类分担不同的任务,我们才可以编写具有更高可替换性的类。 当然这里也是存在层次结构的。例如:当子类 ConcreteClass 实现了父类 AbstractClass 类的抽象方法时,它们之间就构成了一个简单的层次结构。
classDiagram
AbstractClass <|-- ConcreteClass
但是这里的类的层次结构并不是为了增加功能,这种层次结构是帮助我们实现更清晰的任务分担。 父类通过声明抽象方法来定义接口 API,子类通过实现具体方法来实现接口 API。
这种层次结构就称为 类的实现层次结构。
当我们用其他的方式实现 AbstractClass 时,比如实现一个 AnotherConcreteClass 类,类的层次结构也会发生一些变化。
classDiagram
AbstractClass <|-- ConcreteClass
AbstractClass<|-- AnotherConcreteClass
层次结构的混杂与分离
以上就是对两种层次结构的介绍,当我们想要编写子类时,就需要确认自己的意图,是为了新增功能还是为了增加实现?当类的层次结构只有一层时,这两种层次结构是混杂在一个层次结构中的。这样就容易使累的层次结构变得复杂,也难以更清晰的理解类的层次结构。
因此,我们需要将 “类的功能层次结构” 与 “类的实现层次结构” 分离为两个独立的层次结构。
当然,如果只是简单的将其分开,两者之间就会缺少联系,所以我们需要在两者之间搭建一座桥梁,也就是以下要讲解的桥接模式。
示例
类图介绍
首先我们来看一个简单的类关系图:
1.我们定义了 Display 是负责显示的类,其有一个子类 CountDisplay,继承自 Display 并扩展了改类,新增了 multiDisplay 功能。这两个类就完成了 类的功能层次结构 的搭建。 2.DisplayInterface 定义了接口 API,StringDisplayImpl 则有具体的实现,他们完成了 类的实现层次结构 的搭建。
类的功能层次结构 Display
Display 类负责显示一些东西,位于功能层次结构的最上次,impl 字段保存的是实现了 DisplayInterface 类具体功能的实例,该实例通过构造函数传递给 Display,并保存在 impl 中以供后续使用,impl 字段就是类的两个层次结构之间的那座桥梁。
open,print,close 这三个方法是 Display 提供的接口。注意,这三个方法都调用了 impl 字段的实现方法,这样 Display 的接口就被转换成了 DisplayInterface 的接口。
public class Display{
private DisplayInterface impl;
public Display(DisplayInterface impl){
this.impl = impl
}
public void open(){
impl.rawOpen();
}
public void print(){
impl.rawPrint();
}
public void close(){
impl.rawClose();
}
public void display(){
open();
print();
close();
}
}
类的功能层次结构 CountDisplay
接着继续看功能层级结构中的另一个类 CountDisplay。这个类在 Display 的基础上增加了一个新的功能。Display 只有显示的功能,CountDisplay 则有按次数显示的功能 multiDisplay 方法。
class CountDisplay extends Display {
public CountDisplay(DisplayInterface impl) {
super(impl);
}
// 新功能
public void multiDisplay(int times){
open();
for (int i = 0;i<times;i++) {
print();
}
close();
}
}
类的实现层次结构 DisplayInterface
我们将目光移到桥梁的另一侧,类的实现功能层次结构。 DisplayInterface 位于实现层次结构的最上层,是一个抽象的接口,声明了rawOpen、rawPrint、rawClose 这样三个抽象方法,分别与 Display 的三个方法相对应。
interface DisplayInterface{
void rawOpen();
void rawPrint();
void rawClose();
}
类的实现层次结构 StringDisplayImpl
这个类是具体的实现,所有的功能最终都由这个类进行真正的处理。
class StringDisplayImpl implements DisplayInterface{
private String str;
public StringDisplayImpl(String str){
this.str = str;
}
@Override
public void rawOpen() {
System.out.println("open");
}
@Override
public void rawPrint() {
System.out.println(str);
}
@Override
public void rawClose() {
System.out.println("close");
}
}
测试类
public static void main(String[] args) {
Display d1 = new Display(new StringDisplayImpl("hello"));
d1.open();
d1.print();
d1.close();
CountDisplay d2 = new CountDisplay(new StringDisplayImpl("hello"));
d2.multiDisplay(10);
}
以上就完成了一个最简单的桥接模式的构建。
总结
桥接模式中的各个角色
- Abstraction(抽象化):该角色位于类的功能层次结构的最上层,使用 Implementor 角色的方法定义了基本的功能,该角色中保存了 Implementor 角色的实例。也就是实例程序中的 Display。
2.RefinedAbstraction(扩展后的抽象):在 Abstraction 基础上增加新的具有新功能的角色。
-
Implementor(实现者):位于实现层次结构的最上层,定义了用于实现 Abstraction 角色的接口 API,由 DisplayInterface 扮演。
-
ContreteImplementor(具体实现者):实现了 Implementor 中的接口。
分离的好处
桥接模式将两种层次结构进行分离,这样更有利于独立的对它们进行扩展,当我们想要新增功能的时候,只需要在功能层次结构一侧进行增加即可,不必对类的实现层次接口做任何修改。同样,当我们对具体 API 的实现要做修改时,也只需要新增新的实现类就可以,功能层次接口也不需要任何修改。
委托和继承
使用继承很容易对类进行扩展,但是类之间也会形成一种强关联的关系,比如 SomethingGood 和 Something 的父子关系,除非修改继承关系,否者这种关联是无法修改的。
如果想轻松的去改变类之间的关系,使用继承就不合适了,这时,就可以使用委托来代替继承。
实例程序中的 Display 中就使用了委托,其中的 impl 字段保存了实现的实例,这样类的任务就发生了转移。 调用 open 方法会调用 impl 的 rawOpen 方法,调用 close 方法会调用 impl 的 rawClose 方法。 也就是当其他类要用 Display 工作时,Display 并未自己工作,而是将工作交给 impl。这就是委托关系。只有 Display 类的实例生成时,才与作为参数被传入的类构成关联。在示例程序中,Main 类生成 Display 和 CountDisplay 实例时,才将 StringDisplayImpl 的实例作为参数传递给 Display 和 CountDisplay,这里就可以随意修改传入的参数,可以传入一个其他的 DisplayInterface 的实现类,这样就可以很轻松的实现改变。
继承是强关联关系,委托是弱关联关系,这一点同样很重要。
参考资料
《图解设计模式》