【设计模式|桥接模式】桥接模式是一种结构型设计模式,英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:Decouple an abstraction from its implementation so that the two can vary independently。翻译成中文就是:将抽象和实现解耦,让它们可以独立变化。单从官方概念来理解,相当晦涩,通常对抽象与实现的第一反应就是接口(或抽象类)与实现类的继承关系,接口和实现独立变化就更加让人难以理解。
关于桥接模式,还有另外一种理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。再配合类图(https://zh.wikipedia.org/wiki/%E6%A9%8B%E6%8E%A5%E6%A8%A1%E5%BC%8F)就比较容易理解一些。
文章图片
桥接模式所涉及的角色有:
- 抽象(Abstraction)角色:抽象角色,采用抽象类方式,并持有一个实现角色对象的引用。
- 具体抽象(RefinedAbstraction)角色:具体抽象角色具体实现。
- 实现(Implementor)角色:给出实现角色的接口。实现化角色应当只给出抽象层操作,抽象角色持有抽象角色并只给出基于抽象角色的操作。
- 具体实现(ConcreteImplementor)角色:这个角色给出实现角色接口的具体实现。
接下来通过代码理解一下桥接模式,当我们办理银行卡时,有不同的银行(工商银行,建设银行)可选择,每个银行有不同的卡(储蓄卡,信用卡),所以银行和卡就相当于两个独立变化的维度,我们将银行和卡进行抽象,将银行作为抽象角色,将卡作为实现角色,通过组合的方式使银行持有卡的接口引用,将其组合在一起。
public class BridgePattern {/**
* 银行抽象类,桥接模式的抽象部分
*/
abstract static class Bank {
protected Card card;
public Bank(Card card) {
this.card = card;
}abstract Card applyCard();
}/**
* 建设银行实现类
*/
static class CCBBank extends Bank {public CCBBank(Card card) {
super(card);
}@Override
Card applyCard() {
System.out.print("申请建设银行 ");
card.applyCard();
return card;
}
}/**
* 工商银行实现类
*/
static class ICBCBank extends Bank {public ICBCBank(Card card) {
super(card);
}@Override
Card applyCard() {
System.out.print("申请工商银行 ");
card.applyCard();
return card;
}
}/**
* 银行账号,桥接模式的实现部分接口
*/
interface Card {
/**
* 申请卡
*
* @return
*/
Card applyCard();
/**
* 查看卡类型
*/
void showCardType();
}/**
* 信用卡
*/
static class CreditCard implements Card {
@Override
public CreditCard applyCard() {
return new CreditCard();
}@Override
public void showCardType() {
System.out.println("信用卡");
}
}/**
* 储蓄卡
*/
static class DebitCard implements Card {
@Override
public DebitCard applyCard() {
return new DebitCard();
}@Override
public void showCardType() {
System.out.println("储蓄卡");
}
}public static void main(String[] args) {
Bank icbcBank = new ICBCBank(new CreditCard());
Card icbcCard = icbcBank.applyCard();
icbcCard.showCardType();
Bank ccbBank = new CCBBank(new DebitCard());
Card ccbCard = ccbBank.applyCard();
ccbCard.showCardType();
}}
输出结果
申请工商银行 信用卡
申请建设银行 储蓄卡
通过使用桥接模式,就使得银行和卡这两个维度可以独立变化,互不干扰。
JDBC数据库访问接口API正是经典的桥接模式实现,具体的代码如下所示:
Class.forName("com.mysql.jdbc.Driver");
// 加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/mydb?user=root&password=****";
Connection con = DriverManager.getConnection(url);
如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 便可。
com.mysql.jdbc.Driver类代码实现如下:
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
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!");
}
}
}
这里涉及另外一个知识,Class.forName和ClassLoader加载类的区别:ClassLoader只能将类加载到JVM方法区(JDK8为元数据区),但是Class.forName除了加载类外,还可以直接加载类中的静态代码块。
所以当执行 Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,会将 com.mysql.jdbc.Driver 注册到 DriverManager 类中。
java.sql.DriverManager类代码精简后实现如下:
package java.sql;
public class DriverManager {private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
private DriverManager() {
}public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {registerDriver(driver, null);
}public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {/* Register the driver if it has not already been added to our list */
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}public static Connection getConnection(String url, String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}private static Connection getConnection(String url, java.util.Properties info, Class> caller) throws SQLException {
for (DriverInfo aDriver : registeredDrivers) {
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);
}
}
}}
可以看到注册的过程无非是把Driver对象存放进入了CopyOnWriteArrayList类型的registeredDrivers变量中,获取连接的时候,从registeredDrivers中取出对应的Driver并调用相应的connect方法进行连接。
JDBC 这个例子中,JDBC DriverManager就相当于“抽象”角色,java.sql.Driver相当于实现角色,具体的 Driver(比如com.mysql.jdbc.Driver)就相当于实现角色的具体实现。JDBCDriverManager 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。
JDBC的类族是由SUN公司设计了一套接口,再由各个数据库公司实现接口,我们在调用的过程中只需要使用接口去定义,然后在加载Driver的过程中底层代码会给我们选择好接口真正的实现类,以此来实现真正的数据库连接。
总体上来讲,桥接模式的原理比较难理解,但代码实现相对简单。在 GoF 的《设计模式》一书中,桥接模式被定义为:将抽象和实现解耦,让它们可以独立变化。还有另外一种更加简单的理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。对于第一种 GoF 的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的角色,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的角色。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。
桥接模式的优点使抽象和实现可以沿着各自的维度来变化,所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,使它们各自都具有自己的子类,从而获得多维度组合对象。桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
桥接模式的缺点是原理比较难理解,会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。还要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也并非一件易事。
推荐阅读
- Java|Java 传统桥接模式
- 设计模式|Java设计模式-桥接模式 理论代码相结合
- 设计模式|(设计模式十)java设计模式之桥接模式
- java|主题(JAVA 桥接模式)
- java|教你代码实现抢红包功能
- 面经|任拓数据科技 软件开发工程师 一面 万兴科技 前端开发工程师 一二面hr面
- JAVA|hbase高性能读取数据
- 高性能|高性能软件系统设计中应该考虑的问题
- 面试官(MySQL 中的 varchar 最多能存储多少个字符(大部分人都会答错。。。))