在了解桥接模式之前,我们试着理解的这个名称的含义。“桥接”(Bridge Design Pattern),有连接引用之意。在GoF的《设计模式》一书中,是这样描述的 “将抽象和实现解耦,让它们可以独立变化”。抽象与实现解耦我的理解是,用连接替换继承,减少父类对子类的侵入性。 另外一种说法是一个事物属性的对维度解耦,举个例子我现在要买一台手机,首先我对手机的颜色有要求,其次我对其拍照的像素也有要有,最后可能我还会对手机的品牌再有些要求。这时候手机这个对象,就抽出了三个维度属性,程序设计层面如果按照传统的继承的方式来实现,我们使用方动态获取不同维度属性组合的手机,就会造成类爆炸。如果在增加其他维度的属性拓展性也是问题。下面通过具体的结构和模式讲解,具体体会一下桥接的设计哲学。
桥接模式的定义与特点
桥接(Bridge)模式的定义如下:
- 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接模式的结构与实现
模式结构:
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
结构图:
代码示例: 这里按照不同维度属性的方式,试着去方便大家理解一下桥接模式的使用;模拟当前我要买一款新的手机,对手机的颜色和品牌是有要求的。通过桥接模式模拟我选择手机的场景。
- 实现化(Implementor)颜色
public interface Color {
String showColor();
}
- 具体实现化(Concrete Implementor) 这里模拟两种颜色 颜色1.
public class BlackColor implements Color{
@Override
public String showColor() {
return "黑色";
}
}
颜色2
public class SilverColor implements Color{
@Override
public String showColor() {
return "银色";
}
}
- 抽象化(Abstraction) 手机角色,并对颜色引用
public abstract class AbstractMobile {
// 实现化角色
protected Color color;
protected AbstractMobile(Color color) {
this.color = color;
}
/**
* 手机品牌
*/
public abstract void brand();
}
- 具体实现化(Concrete Implementor) 这里实现两种手机品牌 1.品牌1Huawei
public class HuaweiMobile extends AbstractMobile{
protected HuaweiMobile(Color color) {
super(color);
}
@Override
public void brand() {
System.out.println(color.showColor() + "Huawei mobile");
}
}
2.品牌2 Iphone
public class Iphone extends AbstractMobile {
protected Iphone(Color color) {
super(color);
}
@Override
public void brand() {
System.out.println(color.showColor() + "IPhone mobile");
}
}
- 使用方 client
public class Client {
public static void main(String[] args) {
//想要得到黑色iphone
Color color = new BlackColor();
AbstractMobile iphone = new Iphone(color);
iphone.brand();
}
}
执行结果:
黑色IPhone mobile
案例类结构图:
桥接模式在源码中的应用
1.JDBC连接,驱动类Driver为桥接对象 例:
//使用原生jdbc连接数据库
// 1.反射机制加载驱动类
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接Connection
conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 3. 获取sql语句的对象Statement
Statement stmt = conn.createStatement();
// ....
首先看下为什么通过 DriverManager
类能够获取到数据库的连接,这个驱动是什么时候注册进去的。
以mysql的Driver为例
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 注册驱动
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
注册驱动DriverManager.registerDriver(new Driver())
,然后看下DriverManager.getConnection()
方法。
// List of registered JDBC drivers
// Driver中的注册 就是注册到这个集合中
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
// 获取连接
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 略
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 根据驱动获取指定连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
// 略
Driver的connect()
方法,通过不同驱动类会调用不同的连接,各数据库厂商只需要按照自己的方式去实现这个方法,就能完成程序JDBC的连接。
这里理解起来可能有点难,抽象和实现解耦,可以独立变化
,我们秉承桥接的设计思想,理解一下这里所说的抽象
应该就是我们具体的JDBC规范,与具体的实现厂商无关,不管是mysql还是oracle,他们都是基于JDBC规范来开发,Driver
就相当于各厂商的实现,这里的实现也不是我们概念中说的实现
,概念中所说的实现是可以独立变化,并不会对规范所影响,DriverManager
就是我们的桥,组装抽象部分,这里就是各厂商实现的驱动类,JDBC所有的操作,都会委托给Driver
执行.
桥接模式的优缺点
桥接(Bridge)模式的优点是:
- 抽象与实现分离,扩展能力强
- 符合开闭原则
- 符合合成复用原则
- 其实现细节对客户透明
缺点是: 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
桥接模式的应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
桥接模式通常适用于以下场景。
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式的一个常见使用场景就是替换继承 继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。
因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。