桥接模式的简单介绍
桥接模式也称为桥梁模式,是结构型设计模式之一。在现实生活中大家都知道“桥梁”是连接河道两岸的主要交通枢纽,简而言之其作用就是连接河的两边,而我们的桥接模式与现实中的情况很相似,也会承担这连接“两边”的作用,那么具体是哪两边呢?这里先不着急,我们先来看看该模式的定义。
桥接模式的定义
将抽象部分与实现部分分离,使它们都可以独立地进行变化。
桥接模式的使用场景
从模式的定义中我们大致可以了解到,这里“桥梁”的作用其实就是连接“抽象部分” 与“实现部分”,但是事实上,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以通过桥接模式使它们在抽象层建立一个关联关系。
桥接模式的 UML 类图
角色介绍:
- Abstraction:抽象部分,该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现,该类一般为抽象类。
- RefinedAbstraction:优化的抽象部分,抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展。
- Implementor:实现部分,可以为接口或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法。
- ConcreteImplementorA、ConcreteImplementorB:实现部分的具体实现,完善实现部分中方法定义的具体逻辑。
桥接模式实战
现实生活中有很多桥接模式应用的影子,比如开关与具体的电器,开关的类型有多种,而电器也是各式各样,这两者是独立变化的且又有耦合。还有程序员天天面对的显示屏,对于显示屏来说它的尺寸与生产商之间也是一种二维关系,具体的尺寸与具体的厂商独立变化。而更贴近生活的例子就是我们在喝咖啡时,大家知道去咖啡馆喝咖啡一般分为 4 种。大杯加糖、大杯不加糖、小杯加糖、小杯不加糖,对于一杯咖啡来说这 4 种实质上就两种变化,一是大杯小杯,二是加糖不加糖,不管怎样,我们先来定义一个咖啡类。
public abstract class Coffee {
protected CoffeeAdditives impl;
public Coffee(CoffeeAdditives impl){
this.impl = impl;
}
/**
* 咖啡具体是什么样子有子类决定
*/
public abstract void makeCoffee();
}
Coffee 类中保持了对 CoffeeAddITIves 的引用,以便调用具体的实现。同样地,咖啡还分大杯小杯,定义两个子类继承于 Coffee。
public class LargeCoffee extends Coffee {
public LargeCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
Log.d("Coffee", "大杯的" + impl + "咖啡");
}
}
public class SmallCoffee extends Coffee {
public SmallCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
Log.d("Coffee", "小杯的" + impl + "咖啡");
}
}
而对于加进咖啡中的糖,当然也可以选择不加,我们也用以抽象类定义:
public abstract class CoffeeAdditives {
/**
* 具体要往咖啡里添加什么也是有子类实现
* @return 具体添加的东西
*/
public abstract String addSomething();
}
注意,这里的 CoffeeAdditives 其实就对应于上面我们 UML 类图中的实现部分,而 Coffee 则对应于抽闲部分,其实模块定义中所谓的 “抽象” 与“实现”实质上对应的是两个独立变化的维度,因此,上面我们也曾说过,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。在本例中,我们的 Coffee 类虽然是一个抽象类,但它并非是所谓的“抽象部分”,而 CoffeeAdditives 类也并非一定就是“实现部分”,两者各自为一维度,独立变化,仅此而已,所谓的 “抽象与实现分离”更偏向于我们实际的开发,两者并不一定挂钩,这里其实就可以看到桥接模式的应用性其实很广泛,并不局限于程序设计。我们再来看 CoffeeAdditives 对应的两个子类:加糖与不加糖:
public class Sugar extends CoffeeAdditives {
@Override
public String addSomething() {
return "加糖";
}
}
public class Ordinary extends CoffeeAdditives {
@Override
public String addSomething() {
return "原味";
}
}
不加糖我们以原味表示,最后来看客户类,将两者进行整合:
public class Client {
public static void main(){
//原味
CoffeeAdditives ordinary = new Ordinary();
//糖类
CoffeeAdditives sugar = new Sugar();
//大杯咖啡 原味
Coffee largeCoffeeOrdinary = new LargeCoffee(ordinary);
largeCoffeeOrdinary.makeCoffee();
//大杯咖啡 加糖
Coffee largeCoffeeSugar = new LargeCoffee(sugar);
largeCoffeeSugar.makeCoffee();
//小杯咖啡 原味
Coffee smallCoffeeOrdinary = new SmallCoffee(ordinary);
smallCoffeeOrdinary.makeCoffee();
//小杯咖啡 加糖
Coffee smallCoffeeSugar = new SmallCoffee(sugar);
smallCoffeeSugar.makeCoffee();
}
}
代码逻辑很简单,不难理解,这里输出结果就不给出了。如果此时咖啡庁为了满足更多人的习惯,推出中杯的咖啡怎么办?对于本例来说,这种需求的变化其实就是 Coffee 来的变化,定义中杯类扩展 Coffee 类即可:
public class MiddleCoffee extends Coffee {
public MiddleCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
Log.d("Coffee", "中杯的" + impl + "咖啡");
}
}
对应的客户端做出相应的增加即可。
//小杯咖啡 原味
Coffee middleCoffeeOrdinary = new MiddleCoffee(ordinary);
middleCoffeeOrdinary.makeCoffee();
//小杯咖啡 加糖
Coffee middleCoffeeSugar = new MiddleCoffee(sugar);
middleCoffeeOrdinary.makeCoffee();
同样地,为了增加咖啡口味的种类,我们也可以让 CoffeeAdditives 类变化起来,增加更多的子类表示,诸如加奶、加蜂蜜、加盐等,具体的代码就不在给出了,相信大家已经很清楚。从本例中我们可以看到,不管是 Coffee 变化了还是 CoffeeAdditives 变化了,其相对于对方而言都是独立的没有什么过多的交集,两者之间唯一的联系就是 Coffee 中保持的对 CoffeeAdditives 的引用,此乃两者之纽带,这就是桥接模式。
总结
桥接模式可以应用到开发中,但是它应用得并不多,一个很重要的原因是对于抽象与实现分离的把握,是不是需要分离、如何分离?对于设计者来说要有一个恰到好处的分寸。不管怎么说,桥接模式的优点我们母庸置疑,分离抽象与实现、灵活的扩展以及对客户来说透明的实现等。但是使用桥接模式也有一个明显的缺点,上面我们也提到了,就是不容易设计,对开发者来说要有一定的经验要求。因此,对桥接模式应用来说,理解很简单,设计却不容易。