设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
第一种设计方案是为每一种形状都提供一套各种颜色的版本。
第二种设计方案是根据实际需要对形状和颜色进行组合。
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
定义:桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
结构:
- Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
- RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
- Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。 Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
- ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
UML图:
模式分析:画完UML图的时候联想到之前写的工厂方法模式有点相似的地方,两个抽象类,然后分别有对应的子类去继承。但是还是有很大的区别,工厂方法模式重在创建对象的方式;桥接模式主要是对一个对象的抽象,然后将这个对象分为抽象部分和实现部分,使得二者独立变化。例如,车这个类型(对应 Abstraction 类),有几种品牌(对应 RefinedAbstraction类),每种品牌继承车这个类,然后车上安装的零件不一样会得到不同的车型,比如引擎,那么这里的引擎可以抽象(对应 Implementor类),具体的引擎型号对应(ConcreteImplementorA或者ConcreteImplementorB).这就是桥接模式。另外这里提个问题,如果将抽象的类(即引擎对应Abstraction,品牌对应Implementor)交换一下合理吗?
代码分析:
public abstract class Engine {
// 安装引擎
public abstract void installEngine();
// 启动引擎
public abstract void startEngine();
}
// 安装引擎
public abstract void installEngine();
// 启动引擎
public abstract void startEngine();
}
public class BenzEngine
extends Engine {
public final String TAG = "BenzEngine";
@Override
public void installEngine() {
Log. d( TAG, " 安装奔驰引擎 ");
}
@Override
public void startEngine() {
Log. d( TAG, " 启动奔驰引擎 ");
}
}
public final String TAG = "BenzEngine";
@Override
public void installEngine() {
Log. d( TAG, " 安装奔驰引擎 ");
}
@Override
public void startEngine() {
Log. d( TAG, " 启动奔驰引擎 ");
}
}
public class BWMEngine
extends Engine {
public final String TAG = "BWMEngine";
@Override
public void installEngine() {
Log. d( TAG, " 安装宝马引擎 ");
}
@Override
public void startEngine() {
Log. d( TAG, " 启动宝马引擎 ");
}
}
public final String TAG = "BWMEngine";
@Override
public void installEngine() {
Log. d( TAG, " 安装宝马引擎 ");
}
@Override
public void startEngine() {
Log. d( TAG, " 启动宝马引擎 ");
}
}
public abstract class Vehicle {
// 定义一个抽象的引擎
private Engine engine;
// 构造函数,有子类定义具体的引擎
public Vehicle(Engine engine){
this. engine = engine;
}
// 车辆行驶
public void run(){
// 每种车型都要安装引擎
this. engine.installEngine();
// 然后启动引擎
this. engine.startEngine();
}
}
// 定义一个抽象的引擎
private Engine engine;
// 构造函数,有子类定义具体的引擎
public Vehicle(Engine engine){
this. engine = engine;
}
// 车辆行驶
public void run(){
// 每种车型都要安装引擎
this. engine.installEngine();
// 然后启动引擎
this. engine.startEngine();
}
}
public class SportsCar
extends Vehicle{
public final String TAG = "SportsCar";
public SportsCar(Engine engine) {
super(engine);
}
@Override
public void run() {
super.run();
Log. d( TAG, " 跑车在行驶。。。 ");
}
}
public final String TAG = "SportsCar";
public SportsCar(Engine engine) {
super(engine);
}
@Override
public void run() {
super.run();
Log. d( TAG, " 跑车在行驶。。。 ");
}
}
public class Tractor
extends Vehicle {
public final String TAG = "Tractor";
public Tractor(Engine engine) {
super(engine);
}
@Override
public void run() {
super.run();
Log. d( TAG, " 拖拉机在飞快行驶。。。 ");
}
}
public final String TAG = "Tractor";
public Tractor(Engine engine) {
super(engine);
}
@Override
public void run() {
super.run();
Log. d( TAG, " 拖拉机在飞快行驶。。。 ");
}
}
客户端调用:
//
安装宝马引擎的跑车
SportsCar sportsCar = new SportsCar( new BWMEngine());
// 正在行驶
sportsCar.run();
// 安装奔驰引擎的拖拉机,要飞了。。。
Tractor tractor = new Tractor( new BenzEngine());
// 行驶
tractor.run();
SportsCar sportsCar = new SportsCar( new BWMEngine());
// 正在行驶
sportsCar.run();
// 安装奔驰引擎的拖拉机,要飞了。。。
Tractor tractor = new Tractor( new BenzEngine());
// 行驶
tractor.run();
优点:
- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进 行设计与编程。桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
适用环境:
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
扩展:
适配器模式与桥接模式的联用:
- 桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色, 使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式, 特别是那些涉及到大量第三方应用接口的情况。