桥接模式
本文内容
前言
桥接模式在日常开发中使用得并不多,而且理解起来也有难度,所以下面只是简单地介绍下桥接模式,以及它怎么运用。
1. 什么是桥接模式
桥接模式 的定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
这个定义可以说是非常的抽象了,它还有另一种比较简单的理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来替代继承关系,避免子类过多过杂。
可以发现,桥接模式主要是用于将两个独立的模块,通过组合的方式连接起来使用。
2. 桥接模式例子
下面用一个实际生活中的例子来讲解。假设我们是负责给星巴克做订单系统的外包码农,星巴克在一开始时说他们的咖啡只提供正常杯(中杯)、原味和加糖这几种选择。
这个需求还是很简单的,我们先定义一个点咖啡的接口,通过一个下单方法,至于点哪种口味的咖啡,由子类去决定即可:
// 点咖啡的接口
public interface ICoffee {
void orderCoffee(int count);
}
// 原味咖啡类
public class CoffeeOriginal implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("原味咖啡%d杯",count));
}
}
// 加糖咖啡类
public class CoffeeWithSugar implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("加糖咖啡%d杯",count));
}
}
过了没多久,星巴克又过来说,他们还要再添加两个规格容量的咖啡,大杯和小杯。
虽然我们之前是面向抽象编程的,但是现在口味的组合就很多了,有小杯的原味和加糖、中杯的原味和加糖、大杯的原味和加糖,这就需要 6 个实现类。而如果后续还有其他需求,比如三分糖、五分糖、七分糖等等。这实现类不得爆炸?
此时,桥接模式就能派上用场了。这里有两个变化维度,咖啡的容量和口味,而且都是可以独立变化的。
桥接模式的设计结构图如下:
桥梁模式所涉及的角色有:
- 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
- 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
- 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现。
该案例的整体设计如下(本例中无需扩展抽象化角色):
代码实现如下:
public interface ISugarDegree {
void chooseSugarDegree();
}
public class SugarDegreeAsc implements ISugarDegree {
@Override
public void chooseSugarDegree() {
System.out.println("加糖");
}
}
public class SugarDegreeNormal implements ISugarDegree {
@Override
public void chooseSugarDegree() {
System.out.println("正常糖");
}
}
public abstract class Coffee {
// 通过组合的方式将 Coffee 的糖度添加进来
protected ISugarDegree sugarDegree;
// 通过依赖注入将 ICoffeeAdditives 注入进来
public Coffee(ISugarDegree sugarDegree) {
this.sugarDegree = sugarDegree;
}
public abstract void orderCoffee(int count);
}
public class LargeCoffee extends Coffee {
public LargeCoffee(ISugarDegree sugarDegree) {
super(sugarDegree);
}
@Override
public void orderCoffee(int count) {
sugarDegree.chooseSugarDegree();
System.out.println(String.format("大杯咖啡%d杯", count));
}
}
public class MediumCoffee extends Coffee {
public MediumCoffee(ISugarDegree sugarDegree) {
super(sugarDegree);
}
@Override
public void orderCoffee(int count) {
sugarDegree.chooseSugarDegree();
System.out.println(String.format("中杯杯咖啡%d杯", count));
}
}
测试类:
public class Test {
public static void main(String[] args) {
Coffee largeWithSugarDegreeNormal = new LargeCoffee(new SugarDegreeNormal());
largeWithSugarDegreeNormal.orderCoffee(3);
}
}
/* 输出:
正常糖
大杯咖啡3杯
*/
现在我们如果需要添加容量,比如小杯、女神杯等,则直接添加一个类,继承 Coffee 即可。或者添加糖度,比如三分糖、七分糖,也只需要添加一个类,实现 ISugarDegree 接口即可。
不知道你有没有发现,其实 桥接模式就是通过组合的方式,将 M*N 个子类/实现类简化成了 M+N 个,从而大大减少了类的个数。
3. 总结
桥接模式的理解和使用是比较困难的,使用的场景也不多,但是还是需要了解一下。
桥接模式的定义有两种不同的理解:
- 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。
第二种方式比较好理解,其实就是类似于 “组合优于继承” 的设计原则,通过 组合的方式来替代继承,从而避免当需求扩充时,导致子类/实现类的爆炸增长。
桥接模式最大的作用无非就是 通过组合的方式,将 M*N 个子类/实现类简化成了 M+N 个,从而大大减少了类的个数。