桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
假设某个汽车厂商生产三种品牌的汽车:Big、Tiny和Boss,每种品牌又可以选择燃油、纯电和混合动力。如果用传统的继承来表示各个最终车型,一共有3个抽象类加9个最终子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌───────┐ │ Car │ └───────┘ ▲ ┌──────────────────┼───────────────────┐ │ │ │ ┌───────┐ ┌───────┐ ┌───────┐ │BigCar │ │TinyCar│ │BossCar│ └───────┘ └───────┘ └───────┘ ▲ ▲ ▲ │ │ │ │ ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐ ├─│ BigFuelCar │├─│ TinyFuelCar │├─│ BossFuelCar │ │ └───────────────┘│ └───────────────┘│ └───────────────┘ │ ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐ ├─│BigElectricCar │├─│TinyElectricCar│├─│BossElectricCar│ │ └───────────────┘│ └───────────────┘│ └───────────────┘ │ ┌───────────────┐│ ┌───────────────┐│ ┌───────────────┐ └─│ BigHybridCar │└─│ TinyHybridCar │└─│ BossHybridCar │ └───────────────┘ └───────────────┘ └───────────────┘
|
如果要新增一个品牌,或者加一个新的引擎(比如核动力),那么子类的数量增长更快。
所以,桥接模式就是为了避免直接继承带来的子类爆炸。
我们来看看桥接模式如何解决上述问题。
在桥接模式中,首先把Car
按品牌进行子类化,但是,每个品牌选择什么发动机,不再使用子类扩充,而是通过一个抽象的“修正”类,以组合的形式引入。我们来看看具体的实现。
首先定义抽象类Car
,它引用一个Engine
:
1 2 3 4 5 6 7 8 9 10
| public abstract class Car { protected Engine engine;
public Car(Engine engine) { this.engine = engine; }
public abstract void drive(); }
|
Engine
的定义如下:
1 2 3
| public interface Engine { void start(); }
|
紧接着,在一个“修正”的抽象类RefinedCar
中定义一些额外操作:
1 2 3 4 5 6 7 8 9 10 11 12
| public abstract class RefinedCar extends Car { public RefinedCar(Engine engine) { super(engine); }
public void drive() { this.engine.start(); System.out.println("Drive " + getBrand() + " car..."); }
public abstract String getBrand(); }
|
这样一来,最终的不同品牌继承自RefinedCar
,例如BossCar
:
1 2 3 4 5 6 7 8 9
| public class BossCar extends RefinedCar { public BossCar(Engine engine) { super(engine); }
public String getBrand() { return "Boss"; } }
|
而针对每一种引擎,继承自Engine
,例如HybridEngine
:
1 2 3 4 5
| public class HybridEngine implements Engine { public void start() { System.out.println("Start Hybrid Engine..."); } }
|
客户端通过自己选择一个品牌,再配合一种引擎,得到最终的Car:
1 2
| RefinedCar car = new BossCar(new HybridEngine()); car.drive();
|
使用桥接模式的好处在于,如果要增加一种引擎,只需要针对Engine
派生一个新的子类,如果要增加一个品牌,只需要针对RefinedCar
派生一个子类,任何RefinedCar
的子类都可以和任何一种Engine
自由组合,即一辆汽车的两个维度:品牌和引擎都可以独立地变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌───────────┐ │ Car │ └───────────┘ ▲ │ ┌───────────┐ ┌─────────┐ │RefinedCar │ ─ ─ ─>│ Engine │ └───────────┘ └─────────┘ ▲ ▲ ┌────────┼────────┐ │ ┌──────────────┐ │ │ │ ├─│ FuelEngine │ ┌───────┐┌───────┐┌───────┐ │ └──────────────┘ │BigCar ││TinyCar││BossCar│ │ ┌──────────────┐ └───────┘└───────┘└───────┘ ├─│ElectricEngine│ │ └──────────────┘ │ ┌──────────────┐ └─│ HybridEngine │ └──────────────┘
|
桥接模式实现比较复杂,实际应用也非常少,但它提供的设计思想值得借鉴,即不要过度使用继承,而是优先拆分某些部件,使用组合的方式来扩展功能。