桥接

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式的定义非常玄乎,直接理解不太容易,所以我们还是举例子。

假设某个汽车厂商生产三种品牌的汽车:Big、Tiny和Boss,每种品牌又可以选择燃油、纯电和混合动力。如果用传统的继承来表示各个最终车型,一共有3个抽象类加9个最终子类:

  1. ┌───────┐
  2. Car
  3. └───────┘
  4. ┌──────────────────┼───────────────────┐
  5. ┌───────┐ ┌───────┐ ┌───────┐
  6. BigCar TinyCar BossCar
  7. └───────┘ └───────┘ └───────┘
  8. ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐
  9. ├─│ BigFuelCar │├─│ TinyFuelCar │├─│ BossFuelCar
  10. └───────────────┘│ └───────────────┘│ └───────────────┘
  11. ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐
  12. ├─│BigElectricCar │├─│TinyElectricCar│├─│BossElectricCar
  13. └───────────────┘│ └───────────────┘│ └───────────────┘
  14. ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐
  15. └─│ BigHybridCar │└─│ TinyHybridCar │└─│ BossHybridCar
  16. └───────────────┘ └───────────────┘ └───────────────┘

如果要新增一个品牌,或者加一个新的引擎(比如核动力),那么子类的数量增长更快。

所以,桥接模式就是为了避免直接继承带来的子类爆炸。

我们来看看桥接模式如何解决上述问题。

在桥接模式中,首先把Car按品牌进行子类化,但是,每个品牌选择什么发动机,不再使用子类扩充,而是通过一个抽象的“修正”类,以组合的形式引入。我们来看看具体的实现。

首先定义抽象类Car,它引用一个Engine

  1. public abstract class Car {
  2. // 引用Engine:
  3. protected Engine engine;
  4. public Car(Engine engine) {
  5. this.engine = engine;
  6. }
  7. public abstract void drive();
  8. }

Engine的定义如下:

  1. public interface Engine {
  2. void start();
  3. }

紧接着,在一个“修正”的抽象类RefinedCar中定义一些额外操作:

  1. public abstract class RefinedCar extends Car {
  2. public RefinedCar(Engine engine) {
  3. super(engine);
  4. }
  5. public void drive() {
  6. this.engine.start();
  7. System.out.println("Drive " + getBrand() + " car...");
  8. }
  9. public abstract String getBrand();
  10. }

这样一来,最终的不同品牌继承自RefinedCar,例如BossCar

  1. public class BossCar extends RefinedCar {
  2. public BossCar(Engine engine) {
  3. super(engine);
  4. }
  5. public String getBrand() {
  6. return "Boss";
  7. }
  8. }

而针对每一种引擎,继承自Engine,例如HybridEngine

  1. public class HybridEngine implements Engine {
  2. public void start() {
  3. System.out.println("Start Hybrid Engine...");
  4. }
  5. }

客户端通过自己选择一个品牌,再配合一种引擎,得到最终的Car:

  1. RefinedCar car = new BossCar(new HybridEngine());
  2. car.drive();

使用桥接模式的好处在于,如果要增加一种引擎,只需要针对Engine派生一个新的子类,如果要增加一个品牌,只需要针对RefinedCar派生一个子类,任何RefinedCar的子类都可以和任何一种Engine自由组合,即一辆汽车的两个维度:品牌和引擎都可以独立地变化。

  1. ┌───────────┐
  2. Car
  3. └───────────┘
  4. ┌───────────┐ ┌─────────┐
  5. RefinedCar ─>│ Engine
  6. └───────────┘ └─────────┘
  7. ┌────────┼────────┐ ┌──────────────┐
  8. ├─│ FuelEngine
  9. ┌───────┐┌───────┐┌───────┐ └──────────────┘
  10. BigCar ││TinyCar││BossCar ┌──────────────┐
  11. └───────┘└───────┘└───────┘ ├─│ElectricEngine
  12. └──────────────┘
  13. ┌──────────────┐
  14. └─│ HybridEngine
  15. └──────────────┘

桥接模式实现比较复杂,实际应用也非常少,但它提供的设计思想值得借鉴,即不要过度使用继承,而是优先拆分某些部件,使用组合的方式来扩展功能。

练习

使用桥接模式扩展一种新的品牌和新的核动力引擎。

桥接 - 图1下载练习:桥接模式练习 (推荐使用IDE练习插件快速下载)

小结

桥接模式通过分离一个抽象接口和它的实现部分,使得设计可以按两个维度独立扩展。

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论:

桥接 - 图2桥接 - 图3