贫瘠之地

华北无浪漫,死海扬起帆
多少个夜晚,独自望着天

0%

桥接模式 - Bridge

背景

桥接(bridge)模式首先的场景是内部实现逻辑分为多个模块,每个模块又可能对应多种实现

在桥接内部,这些不同类型的模块按照组合或聚合的方式组织在一起,将逻辑模块抽象部分与实现部分相分离

目的 降低内部多种类型逻辑模块的耦合,扩展变成以各模块为单位,更为灵活

现实世界类比 Vans自由定制鞋_Vans(范斯)中国官方网站

球鞋定制,从选择款式开始,对不同的部位(鞋头、鞋腰、侧边条纹、鞋带等)选择不同的设计(颜色、图案、材质等),最终产出最终产品(桥接模式的最终成品可以认为是组合了多个模块逻辑的执行器)

针对不同部位选择不同的设计,而不是平铺出笛卡尔积式的配置表,就是桥接模式希望实现的解耦的目的

实践

模拟一个场景,比如服务内希望实现一个消息通知功能

消息分为多种记录方式:缓存、数据库

通知的方式也分为多种方式:邮件、微信

服务内定义什么级别的消息通过什么方式进行通知

问题

如果将所需要的发送方式进行实现,需要实现 4 种执行器(2 × 2),哪怕是服务中可以进行选择,也需要全部进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CacheEmailNotifier {
public void send(String msg){
System.out.println("缓存数据");
System.out.println("发送电子邮件");
}
}

public class CacheWeChatNotifier {
public void send(String msg){
System.out.println("缓存数据");
System.out.println("发送微信");
}
}
......

如果后续有调整,增加了新的记录方式或者通知方式,那么需要需要创建的类就更多了,并且可能还存在相同的逻辑被实现了多份不便于维护

问题出现的原因是因为将记录方式和通知方式这两种互不关联的模块在实现中耦合在一起

实现

通知方式

定义接口

1
2
3
public interface Sender {
void send(String msg);
}

进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 电子邮件
public class EmailSender implements Sender {
@Override
public void send(final String msg) {
System.out.println("发送电子邮件:" + msg);
}
}

// 微信
public class WeChatSender implements Sender {
@Override
public void send(final String msg) {
System.out.println("发送微信:" + msg);
}
}

记录方式

定义接口

1
2
3
public interface Recorder {
void record(String msg);
}

进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 缓存
public class CacheRecorder implements Recorder {
@Override
public void record(final String msg) {
System.out.println("记录到缓存:" + msg);
}
}

// 数据库
public class DbRecorder implements Recorder {
@Override
public void record(final String msg) {
System.out.println("记录到数据库:" + msg);
}
}

组织者

抽象组织者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AbstractNotifier {

private Recorder recorder;

private Sender sender;

public AbstractNotifier(final Recorder recorder, final Sender sender) {
Assert.notNull(recorder);
Assert.notNull(sender);
this.recorder = recorder;
this.sender = sender;
}

public void handle(String msg) {
recorder.record(msg);
sender.send(msg);
}

}

继承构造模块的实现

1
2
3
4
5
6
7
public class CustomNotifier extends AbstractNotifier {

public CustomNotifier() {
super(new DbRecorder(), new EmailSender());
}

}

业务扩展

需要增加新的发送方式,比如短信

1
2
3
4
5
6
public class ShortMessageSender implements Sender {
@Override
public void send(final String msg) {
System.out.println("发送短信:" + msg);
}
}

需要进行新的 notifier 的实现

1
2
3
4
5
6
7
public class PlusNotifier extends AbstractNotifier {

public PlusNotifier() {
super(new CacheRecorder(), new ShortMessageSender());
}

}

实际应用

JDBC 驱动为所有的关系型数据库提供一个通用的标准,这就是一个桥接模式的典型应用

进行连接

先回顾一下 JDBC 的使用,使用 JDBC 连接 MySQL 数据库主要分为这样几步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1.加载 MySQL 驱动注入到 DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");

//2.提供 JDBC 连接的 URL、用户名和密码
String url = "jdbc:mysql://localhost:3306/test_db?";
String username = "root";
String password = "root";

//3.创建数据库的连接
Connection connection = DriverManager.getConnection(url, username, password);

//4.创建 statement 实例
Statement statement = connection.createStatement();

//5.执行 SQL 语句
String query = "select * from test";
ResultSet resultSet = statement.executeQuery(query);

//6.关闭连接对象
connection.close();

Driver

Class.forName("com.mysql.cj.jdbc.Driver"); 获取了 Driver 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
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() 方法来将 MySQL 驱动注册到 DriverManager

DriverManager

registerDriver() 方法具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 {
// registeredDrivers 是一个 list,用 DriverInfo 实例封装 Driver
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);

}

registeredDrivers 静态变量是一个 list

泛型 DriverInfo 是驱动信息的包装类

1
2
3
4
5
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
//...
}

DriverInfo

DriverInfo 类中封装了 java.sql.Driver 接口,类似 Driver 信息管理者的角色

1
2
3
4
5
6
7
8
9
10
class DriverInfo {

final Driver driver;
DriverAction da;
DriverInfo(Driver driver, DriverAction action) {
this.driver = driver;
da = action;
}
//...
}

Connection

接下来进行连接的获取,Connection connection = DriverManager.getConnection(url, username, password);

Connection 接口是和特定数据库的连接会话,不同的数据库的连接会话都不相同

1
2
3
4
5
public interface Connection  extends Wrapper, AutoCloseable {

Statement createStatement() throws SQLException;
//...
}

通过 DriverManager 中的 getConnection 方法,从 registeredDrivers 进行选择对应数据库驱动下的连接实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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()));
}
// 实际上调用的是下面的静态方法 getConnection
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}

if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}

println("DriverManager.getConnection(\"" + url + "\")");

// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;

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());
}
}

// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}

println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}

Connection 实现

Connection 接口的具体实现部分,MySQL 的连接是通过两层实现完成抽象部分的实现

不同的数据库会进行不同的实现

1
2
3
4
5
6
7
8
9
10
11
public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
private static final long serialVersionUID = 4009476458425101761L;
private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout");
//...
}
public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {
JdbcPropertySet getPropertySet();

void changeUser(String var1, String var2) throws SQLException;
//...
}

总结

DriverManager 作为组织者,组织了 Driver(由管理者 DriverInfo 进行管理)和 Connection 两类模块

根据使用的数据库不同灵活地进行连接的选择

对应的类图:

总结

当存在一段逻辑中分为多类模块时,可以考虑将各模块分别实现再进行组织降低耦合

  • 优点
    • 可以将庞大的类拆分成更有层次的结构,层次之间不存在耦合
    • 便于模块实现的扩展(开闭)
    • 切换不同的实现更加方便
    • 每个模块又可以定义出抽象部分和实现部分(单一职责)
  • 缺点
    • 在已存在的功能上进行改造工程量较大(不同于适配器模式)
    • 高内聚的类进行桥接设计会使代码更加复杂

参考

桥接设计模式 (refactoringguru.cn)

桥接模式的实际使用_最后一个NPE的博客-CSDN博客_桥接模式实战

设计模式学习笔记(九)桥接模式及其应用 - 归斯君 - 博客园 (cnblogs.com)