原理图![](https://img-blog.csdnimg.cn/20211008234439131.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAY29sZHN0YXJyeQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
Bridge 模式定义 :将抽象和行为划分开来,各自独立,但能动态的结合。
总结面向对象实际上就两句话:一是松耦合( Coupling),二是高内聚( Cohesion)。面
向对象系统追求的目标就是尽可能地提高系统模块内部的内聚( Cohesion)、尽可能降低模
块间的耦合( Coupling)。然而这也是面向对象设计过程中最为难把握的部分,大家肯定在
OO 系统的开发过程中遇到这样的问题:
1)客户给了你一个需求,于是使用一个类来实现( A);
2)客户需求变化,有两个算法实现功能,于是改变设计, 我们通过一个抽象的基类,
再定义两个具体类实现两个不同的算法( A1 和 A2);
3)客户又告诉我们说对于不同的操作系统,于是再抽象一个层次,作为一个抽象基类
A0,在分别为每个操作系统派生具体类( A00 和 A01,其中 A00 表示原来的类 A)实现不
同操作系统上的客户需求,这样我们就有了一共 4 个类。
4)可能用户的需求又有变化,比如说又有了一种新的算法……..
5)我们陷入了一个需求变化的郁闷当中,也因此带来了类的迅速膨胀。
Bridge 模式则正是解决了这类问题。
在 Bridge 模式的结构图中可以看到,系统被分为两个相对独立的部分,左边是抽象部
分,右边是实现部分,这两个部分可以互相独立地进行修改:例如上面问题中的客户需求
变化,当用户需求需要从 Abstraction 派生一个具体子类时候,并不需要像上面通过继承
方式实现时候需要添加子类 A1 和 A2 了。另外当上面问题中由于算法添加也只用改变右
边实现(添加一个具体化子类),而右边不用在变化,也不用添加具体子类了
举例:
一杯咖啡为例,子类实现类为四个:中杯加奶、大杯加奶、 中杯不加奶、大杯不加奶。
但是,我们注意到:上面四个子类中有概念重叠,可从另外一个角度进行考虑,这四个类实际是两个角色的组合:抽象 和行为,其中抽象为:中杯和大杯; 行为为:加奶 不加奶(如加橙汁 加苹果汁) .
实现四个子类在抽象和行为之间发生了固定的绑定关系,如果以后动态增加加葡萄汁的行为,就必须再增加两个类:中杯加葡萄汁和大杯加葡萄汁。显然混乱,扩展性极差。
那我们从分离抽象和行为的角度,使用 Bridge 模式来实现。
如何实现?
以上面提到的咖啡 为例. 我们原来打算只设计一个接口(抽象类),使用 Bridge 模式后,我们需要将抽象和行为分开,加奶和不加奶属于行为,我们将它们抽象成一个专门的行为接口
package design.bridge;
/**
* Bridge 模式定义 :将抽象和行为划分开来,各自独立,但能动态的结合
* 一杯咖啡为例,子类实现类为四个:中杯加奶、大杯加奶、 中杯不加奶、大杯不加奶。
* 但是,我们注意到:上面四个子类中有概念重叠,可从另外一个角度进行考虑,
* 这四个类实际是两个角色的组合:抽象 和行为,其中抽象为:中杯和大杯; 行为为:加奶 不加奶(如加橙汁 加苹果汁)
*/
public class BridgePattern {
public static void main(String[] args) {
// 实例行为
CoffeeImp milk = new MilkCoffeeImp();
CoffeeImp sugar = new SugarCoffeeImp();
// 要一个大杯加奶
Coffee coffee = new SuperSizeCoffee(milk);
System.out.println("大杯加牛奶begin");
// 倒咖啡
coffee.pourCoffee();
System.out.println("大杯加牛奶end");
System.out.println("中杯加糖begin");
//要一个中杯加糖
coffee = new MediumCoffee(sugar);
// 倒咖啡
coffee.pourCoffee();
System.out.println("中杯加糖end");
}
}
// 咖啡行为抽象类
abstract class CoffeeImp {
public abstract void pourCoffeeImp();
}
// 咖啡抽象类
abstract class Coffee {
protected CoffeeImp coffeeImp;
public Coffee(CoffeeImp coffeeImp) {
this.coffeeImp = coffeeImp;
}
public abstract void pourCoffee();
}
// 实现咖啡杯子的具体情况,比如中杯,大杯
//中杯
class MediumCoffee extends Coffee {
public MediumCoffee(CoffeeImp coffeeImp) {
super(coffeeImp);
}
public void pourCoffee() {
System.out.println("中杯");
coffeeImp.pourCoffeeImp();
}
}
//大杯
class SuperSizeCoffee extends Coffee {
public SuperSizeCoffee(CoffeeImp coffeeImp) {
super(coffeeImp);
}
public void pourCoffee() {
System.out.println("大杯");
coffeeImp.pourCoffeeImp();
}
}
//咖啡行为实现,加奶
class MilkCoffeeImp extends CoffeeImp {
MilkCoffeeImp() {
}
public void pourCoffeeImp() {
System.out.println("加牛奶");
}
}
//咖啡行为实现,加糖
class SugarCoffeeImp extends CoffeeImp {
SugarCoffeeImp() {
}
public void pourCoffeeImp() {
System.out.println("加糖");
}
}
Bridge 是设计模式中比较复杂和难理解的模式之一, 也是 OO 开发与设计中经常会用到
的模式之一。 使用组合( 委托) 的方式将抽象和实现彻底地解耦, 这样的好处是抽象和实现
可以分别独立地变化,系统的耦合性也得到了很好的降低。
GoF 在说明 Bridge 模式时,在意图中指出 Bridge 模式“将抽象部分与它的实现部分分
离, 使得它们可以独立地变化”。 这句话很简单, 但是也很复杂, 连 Bruce Eckel 在他的大作
《Thinking in Patterns》中说“ Bridge 模式是 GoF 所讲述得最不好( Poorly- described)的模
式”, 个人觉得也正是如此。 原因就在于 GoF 的那句话中的“实现” 该怎么去理解: “实现”
特别是和“抽象” 放在一起的时候我们“默认” 的理解是“实现” 就是“抽象” 的具体子类的实现,但是这里 GoF 所谓的“实现”的含义不是指抽象基类的具体子类对抽象基类中虚函数(接口) 的实现, 是和继承结合在一起的。 而这里的“实现” 的含义指的是怎么去实现用户的需求, 并且指的是通过组合(委托) 的方式实现的, 因此这里的实现不是指的继承基类、实现基类接口,而是指的是通过对象组合实现用户的需求。 理解了这一点也就理解了Bridge 模式,理解了 Bridge 模式,你的设计就会更加 Elegant 了。
实际上上面使用 Bridge 模式和使用带来问题方式的解决方案的根本区别在于是通过继承还是通过组合的方式去实现一个功能需求。因此面向对象分析和设计中有一个原则就是:Favor Composition Over Inheritance。 其原因也正在这里。
JDBC的例子
网络上看到有朋友对JDBC到底是不是应用了Bridge模式有一些分歧。这一点我们还是很明确的,JDBC确实是应用了Bridge模式。
JDBC其实只是Sun定义出来的一套规范,java.sql下的真正内容大部分都是接口。而其他的数据库厂商则可以分别提供自己的jdbc实现来支持自家的数据库。作为应用开发者,完全可以只靠Sun的这套JDBC接口来完成所有的应用开发,而无需关心数据库是哪一家的。作为数据库厂商,只需要提供自家数据库的实现就可以了,完全不需要考虑自家的数据库会被用来做什么样的应用。这也就是抽象(数据库的应用)和实现(各厂商的JDBC实现)的分离。
再说到代码,想想我们用JDBC写数据库应用时,与具体数据库关联的代码只有一行。
这行代码所做的事情也非常简单,就是创建一个自己(com.mysql.jdbc.Driver)的实例,注册到java.sql.DriverManager中。
之后在调用DriverManager.getConnection()时就会从已注册的driver中寻找合适的项并返回。