桥接模式是一种结构型模式,它主要应对的是:由于实际的需要,某个类具有两个或以上维度的变化,如果只是使用继承,将无法实现这种需要,或者是的设计变得更加臃肿。
举例来说,假设现在我们需要为某个餐厅制造菜单,餐厅供应牛肉面、猪肉面……,而且顾客可以根据自己的口味选择是否添加辣椒。此时就产生了一个问题,我们如何来应对这种变化(这已经是两个维度的变化了,一个维度是面里面加的什么肉,另一个维度是加不加辣椒)?我们是否需要定义辣椒牛肉面、无辣牛肉面、辣椒猪肉面、无辣猪肉面4(2^2)个子类?如果还有一个加不加醋的面呢(这是第三个维度)?那不是要定义8(2^3)个子类?想想如果餐厅还供应羊肉面,酸菜面呢?
为了解决这个问题,我们可以使用桥接模式,桥接模式的做法是把变化的部分抽象出来,使变化部分与主类(相当于就是桥接类)分离开来,从而将多个维度的变化彻底分离。最后提供一个管理类来组合不同维度上的变化,通过这种组合来满足业务需要。
下面以一个简单的实例来说明桥接模式的使用。程序先提供了一个Peppery接口,该接口代表是否添加辣椒:
public interface Peppery { String style();}
接着为该接口提供两个实现类,它们分别表示添加辣椒和不添加辣椒:
public class PepperyStyle implements Peppery { @Override public String style() { System.out.println("辣味十足,爽!"); }}
public class PlainStyle implements Peppery { @Override public String style() { System.out.println("清淡一点,养胃"); }}
从上面的接口可以看出,Peppery接口代表了加不加辣椒这个维度的变化,不论面条在该维度上有多少种变化(辣味还可能有微辣、中辣、重辣等),程序只需要为这几种变化分别提供实现类即可。对于系统而言,辣味风格这个维度上的变化是固定的,程序必须面对的 ,程序使用桥接模式将辣味风格这个维度的变化抽象出来,避免了与牛肉、猪肉、羊肉等材料这个维度的变化耦合在一起。
接着程序提供了一个AbstractNoodle抽象类,该抽象类将会持有一个Peppery属性,该属性代表面条的辣味风格。程序通过AbstractNoodle组合一个Peppery对象,从而实现了面条在辣味风格这个维度上的变化,而AbstractNoodle本身可以包含很多实现类,不同实现类则代表了面条在不同材料风格这个维度的变化。下面是AbstractNoodle的代码:
public abstract class AbstractNoodle { protected Peppery style; public AbstractNoodle(Peppery style) { this.style = style; } public abstract void eat();}
正如上面的代码所示,AbstractNoodle类中持有一个Peppery类型的实例,不同的AbstractNoodle实例与不同的Pepper实例组合,就可以完成辣味风格、材料风格两个维度的变化(这里只简单说明这两个维度的变化,更多维度,原理相同)。由此可见,AbstractNoodle抽象类可以看作是一个桥梁,它被用来“桥接”面条的材料风格的改变与辣味风格的改变,是面条的特殊属性得到无绑定的扩充。
接下类为AbstractNoodle提供一个子类PorkyNoodle,该类代表了“猪肉面”:
public class PorkyNoodle extends AbstractNoodle { public PorkyNoodle(Peppery style) { super(style) } @Override public void eat() { System.out.println("这是一碗猪肉面,辣味风格为:" + super.style.style()); }}
再提供一个BeefNoodle,该子类代表了“牛肉面”:
public class BeefNoodle extends AbstractNoodle { public BeefNoodle(Peppery style) { super(style) } @Override public void eat() { System.out.println("这是一碗牛肉面,辣味风格为:" + super.style.style()); }}
从PorkyNoodle和BeefNoodle中可以看出,AbstractNoodle的两个具体类实现eat()方法时,既组合了材料风格的变化,也组合了辣味风格的变化,从而可以表现出两个维度的变化。桥接模式下,这些接口和类之间的结构关系如下:
下面提供一个主程序,可以分别产生辣椒牛肉面、无辣牛肉面、辣椒猪肉面、无辣猪肉面等几种风格的面条:
public class Test { public static void main(String args[]) { //辣椒牛肉面 AbstractNoodle noodle1 = new BeefNoodle(new PepperyStyle()); //无辣牛肉面 AbstractNoodle noodle2 = new BeefNoodle(new PlainStyle()); //辣椒猪肉面 AbstractNoodle noodle3 = new PorkyNoodle(new PepperyStyle()); //无辣猪肉面 AbstractNoodle noodle4 = new PorkyNoodle(new PlainStyle()); noodle1.eat(); noodle2.eat(); noodle3.eat(); noodle4.eat(); }}
上面的main方法中得到了这4中面条,这4种面条满足了面条在两个维度上的变化,而程序结构又足够清晰且扩展性好。
桥接模式在JavaEE架构中有非常广泛的用途,由于JavaEE应用需要实现跨数据库的功能,程序为了在不同数据库之间迁移,因此系统需要在持久化技术这个维度上存在改变;除此之外,系统也需要在不同业务逻辑实现之间迁移,因此也需要在逻辑实现这个维度存在改变,这正好符合桥接模式的使用场景。因此,JavaEE应用都会推荐使用业务逻辑组件和DAO组件分离的结构,让DAO组件负责持久化技术这个维度上的改变,让业务逻辑组件负责业务逻辑实现这个维度上的改变。由此可见,JavaEE应用中常见的DAO模式正是桥接模式的应用。