桥接模式
桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。
The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently.
桥接模式的定义,说完和没说一样,什么是抽象,什么是实现,又怎么了就可以独立变化了,还是用 UML 类图来分析吧。
桥接模式角色如下:
⨳ Abstraction (抽象者):定义了高层抽象部分的接口,其中包含一个向 Implementor 的引用。
⨳ Implementor (实现者):定义了实现部分的接口,供 Abstraction 使用。
⨳ Concrete Implementor(具体实现): 是实现了 Implementor 接口的具体类,实现了具体的操作方法。
⨳ Refined Abstraction (精炼抽象):是 Abstraction 的子类,实现父类中的业务方法,并通过组合关系调用 Implementor 中的业务方法。
呃呃呃,看完 UML 类图,这不就是依赖倒转原则嘛,要针对接口编程,不要对实现编程。抽象者(Abstraction)和实现者(Implementor)作为两个更高层次的抽象进行依赖。
没错,就是这样的。
桥接模式关注的是不同维度的变化。
举个例子,几何形状 Shape 类,有圆形和方形的变化,如果这时再加上其他变化,如三角,没关系加就行。
对于只有一个维度变化的时候,我们增加子类就可以了,但如果有两个维度的变化呢?比如说颜色 Color 类,有红色和蓝色的变化。
如果使用继承实现,每一个维度增加一个变化,增加的类不再是一个了,而是另一个维度变化的个数,如果再增加维度的变化,那增加的类就只能用乘法了,也就是几何增长啦。
桥接模式的真正意思是将不同维度的变化进行隔离,你变你的,我变我的,最后用组合方式关联在一起就好了。
再举不太恰当的一个例子,比如说喝水这个行为,可以分成喝水的容器与喝水的方式两个维度:
⨳ 喝水的容器可以是杯子、可以是筷子、可以是尿壶。。。怎么变无所谓;
⨳ 喝水的方式可以是一口口地喝、可以是舔着喝。。。怎么变也无所谓;
但是如果你是一个精神正常的人,肯定会选择用杯子一口口地喝的组合,,,当然也可以选择其他组合,看你心情喽!
那桥接模式中的“桥接”是什么意思就很明白了,抽象和实现解耦中的“抽象” 不是抽象类、不是接口(不完全是),而是持有其他变化维度的变化维度(如形状),而“实现”也不是对接口的实现类,而是被其他变化维度持有的变化维度(如颜色)。
如果再增加变化维度,桥接模式也可以胜任,只需要让被其他变化维度持有的变化维度再持有一个变化维度就好啦。
闲话少聊,看看基本代码实现。
基本实现
实现者 Implementor
Implementor 定义实现部分的接口,供扩展抽象者调用。
public abstract class Implementor {
public abstract void operation();
}
具体实现 ConcreteImplementor
▪ 具体实现A
public class ConcreteImplementorA extends Implementor{
@Override
public void operation() {
System.out.println("具体实现A的方法执行");
}
}
▪ 具体实现B
public class ConcreteImplementorB extends Implementor{
@Override
public void operation() {
System.out.println("具体实现B的方法执行");
}
}
抽象者 Abstraction
抽象者定义了高层抽象部分的接口,其中包含一个向 Implementor 的引用。
public abstract class Abstraction {
protected Implementor implementor;
public void setImplementor(Implementor implementor){
this.implementor = implementor;
}
public abstract void operation();
}
精炼抽象 RefinedAbstraction
精炼抽象就是 Abstraction 的子类,可以通过组合关系调用实现化角色中的业务方法。
▪ 精炼抽象A
public class RefinedAbstractionA extends Abstraction {
@Override
public void operation() {
System.out.println("精炼抽象A方法执行");
implementor.operation();
}
}
▪ 精炼抽象B
public class RefinedAbstractionB extends Abstraction {
@Override
public void operation() {
System.out.println("精炼抽象B方法执行");
implementor.operation();
}
}
客户端 Client
抽象部分和具体部分可以任意组合。
// 抽象部分
Abstraction abstractionA = new RefinedAbstractionA();
Abstraction abstractionB = new RefinedAbstractionB();
// 具体部分
Implementor implementorA = new ConcreteImplementorA();
Implementor implementorB = new ConcreteImplementorB();
// 抽象A 与 具体A 组合
abstractionA.setImplementor(implementorA);
abstractionA.operation();
System.out.println("*******************");
// 抽象A 与 具体B 组合
abstractionA.setImplementor(implementorB);
abstractionA.operation();
System.out.println("*******************");
// 抽象B 与 具体B 组合
abstractionB.setImplementor(implementorB);
abstractionB.operation();
输出结果如下:
精炼抽象A方法执行
具体实现A的方法执行
*******************
精炼抽象A方法执行
具体实现B的方法执行
*******************
精炼抽象B方法执行
具体实现B的方法执行
源码鉴赏
JDBC 之 Driver
遥想当年,刚开始学习 Java 时,应该都写过 JDBC 连接数据库的代码:
// 加载及注册JDBC驱动程序
Class.forName("com.mysql.jdbc.Driver");
String url = "your url connect to database.";
Connection con = DriverManager.getConnection(url);
如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以获取 Oracle 的连接了。
1)通过 Class.forName 加载 MySQL驱动时,对将驱动注册进 DriverManager;
2)通过 DriverManager 获取连接时,会委托给 MySQL 的驱动获取连接;
JDBC 的驱动怎么就是桥接模式呢?只有一种变化呀。
谁说一种变化不能成为桥接了,JDBC 本身就相当于“抽象”,具体的 Driver 相当于“实现”,无论是 “抽象” 还是 “实现” 都不是指单个接口或单个实现类,而是一整套 “类库”, “抽象” 和 “实现” 解耦就是桥接。
话说回来了,即使将桥接模式理解成单个抽象类和实现类的解耦也可以,毕竟学习学的是“神”而不是“形”。
总结
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
优点如下:
⨳ 解耦抽象和实现:通过将抽象部分与实现部分分离,桥接模式可以使它们可以独立地变化,从而降低它们之间的耦合度。
⨳ 可扩展性:桥接模式使得抽象部分和实现部分可以独立地扩展,而不会相互影响,从而更容易地引入新的抽象和实现。
⨳ 可维护性:由于桥接模式将抽象部分和实现部分分离,使得系统更易于维护和修改。
⨳ 隐藏实现细节:桥接模式可以隐藏实现的细节,使得客户端不需要了解底层的实现细节,只需要与抽象接口进行交互。
缺点如下:
⨳ 增加复杂性:引入了额外的抽象和实现层次,可能增加系统的复杂性。
⨳ 需求变更的难度:如果抽象部分和实现部分经常变化,可能需要频繁地修改抽象部分和实现部分之间的关系,增加了维护成本。
⨳ 不易理解:桥接模式的概念相对较复杂,需要一定的学习成本,不易于理解和掌握。
综合考虑,桥接模式适用于需要将抽象部分和实现部分分离,并且它们都可能频繁变化的情况下,从而提高系统的灵活性和可维护性。