背景
桥接(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 Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://localhost:3306/test_db?" ;String username = "root" ;String password = "root" ;Connection connection = DriverManager.getConnection(url, username, password);Statement statement = connection.createStatement();String query = "select * from test" ;ResultSet resultSet = statement.executeQuery(query);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 { if (driver != null ) { registeredDrivers.addIfAbsent(new DriverInfo (driver, da)); } else { throw new NullPointerException (); } println("registerDriver: " + driver); }
registeredDrivers
静态变量是一个 list
泛型 DriverInfo
是驱动信息的包装类
1 2 3 4 5 public class DriverManager { 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())); } private static Connection getConnection ( String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null ; synchronized (DriverManager.class) { if (callerCL == null ) { callerCL = Thread.currentThread().getContextClassLoader(); } } if (url == null ) { throw new SQLException ("The url cannot be null" , "08001" ); } println("DriverManager.getConnection(\"" + url + "\")" ); SQLException reason = null ; for (DriverInfo aDriver : registeredDrivers) { if (isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null ) { println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null ) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } 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)