桥接模式(Bridge Pattern)也称为桥梁模式,是结构型设计模式之一,“桥梁”是连接河道两岸的主要交通枢纽,而桥接模式与现实中的情况很相似,也是承担着连接“两边”的作用。
定义:
将抽象部分与实现部分分离,使它们都可以独立地进行变化。
使用场景:
任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,可以通过桥接模式使它们在抽象层建立一个关联关系。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,也可以考虑使用桥接模式。
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
UML类图:
Abstraction:抽象部分,该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现,该类一般为抽象类。
RefinedAbstraction:优化的抽象部分,抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展。
Implementor:实现部分,可以为接或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法。
ConcreteInplementorA/B : 实现部分的具体实现,完善实现部分中方法定义的具体逻辑。
示例:
以咖啡为例,对一杯咖啡来说这4种实质上就两种变化,一是大杯小杯,二是加糖不加糖。
public abstract class Coffee {
public CoffeeAdditives impl;
public Coffee(CoffeeAdditives impl) {
this.impl = impl;
}
//咖啡具体是什么样的由子类决定
public abstract void makeCoffee();
}
Coffee类中保持了对CoffeeAdditives的引用,以便调用具体的实现。
public class LargeCoffee extends Coffee{
public LargeCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
System.out.println("大杯的" + impl.addSomething() + "咖啡");
}
}
public class SmallCoffee extends Coffee{
public SmallCoffee(CoffeeAdditives impl) {
super(impl);
// TODO Auto-generated constructor stub
}
@Override
public void makeCoffee() {
System.out.println("小杯的" + impl.addSomething() + "咖啡");
}
}
对于加进咖啡中的糖,当然也可以选择不加,也定义一个抽象类
public abstract class CoffeeAdditives {
/**
* 具体要往咖啡里添加什么也是由子类实现
* @return
*/
public abstract String addSomething();
}
CoffeeAdditives对应于类图中的实现部分,Coffee则对应于抽象部分,模式定义中所谓的“抽象”与“实现”实现上对应的是两个独立变化的维度,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。本例中,Coffee类虽是一个抽象类,但它并非是所谓的“抽象部分”,而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(String[] args) {
//原味
Ordinary implOrdinary = new Ordinary();
//准备糖类
Sugar implSugar = new Sugar();
//大杯 原味
LargeCoffee largeCoffeeOrdinary = new LargeCoffee(implOrdinary);
largeCoffeeOrdinary.makeCoffee();
//小杯原味
SmallCoffee smallCoffeeOrdinary = new SmallCoffee(implOrdinary);
smallCoffeeOrdinary.makeCoffee();
//大杯加糖
LargeCoffee largeCoffeeSugar = new LargeCoffee(implSugar);
largeCoffeeSugar.makeCoffee();
//小杯加糖
SmallCoffee smallCoffeeSugar = new SmallCoffee(implSugar);
smallCoffeeSugar.makeCoffee();
}
//运行结果:
/*大杯的原味咖啡
小杯的原味咖啡
大杯的加糖咖啡
小杯的加糖咖啡*/
}
如果此时为了满足更多人的习惯,推出中杯的咖啡怎么办? 对应于本例来说,这种需求的变化其实就是Coffee类的变化,定义个中杯类扩展Coffee类即可
public class MiddleCoffee extends Coffee{
public MiddleCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
System.out.println("中杯的" + impl.addSomething() + "咖啡");
}
}
Client类中修改代码即可:
//中杯加糖
MiddleCoffee middleCoffeeSugar = new MiddleCoffee(implSugar);
middleCoffeeSugar.makeCoffee();
//中杯原味
MiddleCoffee middleCoffeeOrdinary = new MiddleCoffee(implOrdinary);
middleCoffeeOrdinary.makeCoffee();
为了增加咖啡口味的种类,我们也可以让CoffeeAdditives类变化起来,增加更多的子类表示 加奶....等等,从本例中我们可以看到,不管是Coffee变化了还是CoffeeAdditives变化了,其相对于双方而言都是独立的,没有什么过多的交集,两者之间唯一的联系就是Coffee中保持对CoffeeAdditives的引用,此为两者之间的纽带,这就是桥接模式。
总结:
桥接模式可以应用到些许开发中,但是它应用得却不多,一个很重要的原因是对于抽象与实现的分离的把握,是不是需要分离,如何分离,对设计者来说要有一个恰到好处的分寸,优点就是分离抽象与实现、灵活的扩展以及对客户来说透明的实现等。缺点就是不容易设计,对于开发者来说要有一定的经验要求,因为,对桥接模式应用来说,理解很简单,设计却不容易。