1.定义
1:将抽象和行为相分离,各自可以独立变化,通过动态的结合实现解耦(板桥里人).从对象的构成来定义,一般我们定义对象是属性和行为的组合。
2:将抽象和抽象方法的实现相分离,各自可以独立变化,通过动态的结合实现解耦(GOF).从对象方法角度来定义。比如说通过JDBC访问数据库,我们操作的API是基于接口的,是抽象,没有实现。而特定数据库提供的驱动测试抽象方法的具体实现。
2.Bridge模式的结构图
3.设计中面临的问题
咖啡杯有大有小,咖啡本身可以加牛奶,也可以不加。因此针对以上需求设计如下4个类,中杯加奶、大杯加奶、中杯不加奶、大杯不加奶。层次结构如下。
但是,我们注意到:上面四个子类中有概念重叠,可从另外一个角度进行考虑,这四个类实际是两个角色的组合:抽象和行为,其中抽象为:中杯和大杯;行为为:加奶 不加奶(如加橙汁 加苹果汁).
实现四个子类在抽象和行为之间发生了固定的绑定关系,如果以后有加葡萄汁的行为,就必须再增加两个类:中杯不加葡萄汁和大杯加葡萄汁。如果客户需要添加小杯产品则需要添加2(加或不加)*2(奶,葡萄汁)种类。类的种类会成指数增长,这样扩展性极差,不易维护。
行文至此,突然发现变化的部分其实是添加的饮品。杯子的大小也是可以变化的,但相对与可添加的饮品来说变化要小的多。若以加或不加作为设计不变,因为所有的不加其实都是一样的。设计如下。
同样是11个类,但如果要添加苹果汁第一种则需要额外4个类,第二张则只需要添加2个类。这样同样会带来子类无限增长的噩梦,不容易维护。
4.解决问题
那我们从分离抽象和行为的角度,使用Bridge模式来实现。
先看看抽象部分的接口代码:
public abstract class Coffee {
private CoffeeAdditive coffeeImp;
public void setCoffeeImp(CoffeeAdditive coffeeImp) { this.coffeeImp = coffeeImp; }
public CoffeeAdditive getCoffeeImp() { return this.coffeeImp; }
public abstract void pourCoffee();
} |
其中CoffeeImp是加不加奶的行为接口,看其代码如下:
public abstract class CoffeeImp { public abstract void pourCoffeeImp(); } |
现在我们有了两个抽象类,下面我们分别对其进行继承,实现concrete class:
中杯
public class MediumCoffee extends Coffee{ public void pourCoffee() { // 我们以重复次数来说明是冲中杯还是大杯 ,重复2次是中杯 for (int i = 0; i < 2; i++) { this.getCoffeeImp().pourCoffeeImp(); } } } |
大杯
public class SuperSizeCoffee extends Coffee{ @Override public void pourCoffee() { // 我们以重复次数来说明是冲中杯还是大杯 ,重复3次是大杯 for (int i = 0; i < 3; i++) { this.getCoffeeImp().pourCoffeeImp(); } } } |
加奶
public class MilkCoffeeImp extends CoffeeImp { @Override public void pourCoffeeImp() { System.out.println("加了美味的牛奶"); } } |
什么都不加
public class OnlyCoffeeImp extends CoffeeImp { @Override public void pourCoffeeImp() { System.out.println("什么都没加,清香"); } } |
加葡萄汁
public class GrapeJuiceCoffee extends CoffeeImp { @Override public void pourCoffeeImp() { System.out.println("加葡萄汁,爽口"); } } |
下面示例展示了如何组合一中杯加牛奶的咖啡。
public class Main { public static void main(String[] args) { Coffee coffee = new MediumCoffee(); coffee.setCoffeeImp(new MilkCoffeeImp()); coffee.pourCoffee(); } } |
这样就很容易添加新的饮品或者是杯子的种类,通过组合使双方解耦,达到易维护易扩展的目的。
更新后的代码结构如下(菱形应该是空心的,Visio竟然不支持。)
5.Bridge模式在JDBC中的应用
看到很多关于设计模式的书籍中都提到JDBC使用了Bridge设计模式,下面基于JDK源代码做个简单的分析。众所周知,JDBC只是Sun的技术标准,说白了就是一套API,真正起作用的是各家数据的产商提供的驱动。数据库连接是一个抽象的行为,而起具体的实现是有外部的驱动实现的。符合Bridge模式所解决的问题特质。
在使用JDBC的过程中我们主要是跟DriverManager打交道。注册驱动和获取连接。对应Bridge模式的结构图,DriverManager即Abstraction,没有RefinedAbstraction。而java.sql.Driver对应Impementor,各种类型的数据库驱动则是ConcreteImpementor。
连接数据库的第一个步骤就是加载驱动,然后就是获得数据库连接。也就是说在第一步之后系统已经知道客户使用的数据库类型了。通常大家都是用Class.forName一下数据库驱动,也可以创建一个驱动类的实例然后注册到DriverManager里面。以Oracle数据库驱动为例,看下面代码片段(移除部分代码)。
static { defaultDriver = null; if(defaultDriver == null) { defaultDriver = new OracleDriver(); DriverManager.registerDriver(defaultDriver); } } |
Static方法块在类被加载之后就会执行,也就是说Oracle驱动实际上创建了一个新的实例并注册到DriverManager里面。因此建议使用第一种方式,避免重复调用。
通过加装Oracle数据库驱动把具体的实现组合到DriverManager里面,然后DriverManager会遍历所有注册的驱动并尝试创建连接,然后返回第一个创建成功的数据库连接。
日期:2009-9-9
参考网站:http://www.jdon.com/designpatterns/bridge.htm
参考书籍:《Design Pattern in Java》
《Design Pattern Explained》
附件:Bridge模式类图