设计模式学习笔记桥接模式及其应用

桥接(Bridge)模式是指将抽象部分与实现部分相分离,使它们都可以独立的发生变化。

一、桥接模式介绍

我们知道,抽象部分一般与实现部分连接有两种方式:继承和实现。那么如何将其解耦分离,桥接模式提供一种方式,也就是将强关联转为弱关联,将继承转换为组合关系。如下图所示,取消两者的继承关系,改用组合关系:

设计模式学习笔记桥接模式及其应用

1.1 桥接模式的结构

我们可以看看桥接模式是怎么解耦,利用组合连接抽象和实现部分,如下所示:

设计模式学习笔记桥接模式及其应用

其结构中包含如下角色:

  • Abstraction:抽象化角色,定义抽象类,包含一个对实现化对象的引用(组合)
  • RefinedAbstraction:扩展抽象化角色,实现抽象化角色的子类,由此通过组合关系调用实现化角色中的业务方法
  • Implementor:实现化角色的接口,供扩展抽象化角色调用
  • ImplementorA、ImplementorB:实现化角色的具体实现

1.2 桥接模式的实现

我们可以根据上面的UML图实现对应的代码:

//客户端类 public class Client {     public static void main(String[] args) {         Implementor imple = new ImplementorA();         Abstraction abs = new RefinedAbstraction(imple);         abs.Operation();     } } //实现化角色 interface Implementor {     public void OperationImpl(); } //具体的实现化角色 class ImplementorA implements Implementor {     public void OperationImpl() {         System.out.println("我是具体实现化角色A");     } } class ImplementorB implements Implementor {     public void OperationImpl() {         System.out.println("我是具体实现化角色B");     } } //抽象化角色 abstract class Abstraction {     protected Implementor imple;          protected Abstraction(Implementor imple) {         this.imple = imple;     }          public abstract void Operation(); } //扩展抽象化角色 class RefinedAbstraction extends Abstraction {     protected RefinedAbstraction(Implementor imple) {         super(imple);     }          public void Operation() {         System.out.println("扩展抽象化角色被访问");         imple.OperationImpl();     } }

实现结果:

扩展抽象化角色被访问 我是具体实现化角色A

二、桥接模式的应用场景

2.1 JDBC 驱动器

JDBC为所有的关系型数据库提供一个通用的标准,这就是一个桥接模式的典型应用。我们先回顾一下JDBC的使用,用JDBC连接MySQL数据库主要分为这样几步:

//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";  //查询语句,也可以换成CRUD的其他语句 ResultSet resultSet = statement.executeQuery(query); //6.关闭连接对象 connection.close();

我们一步步来看,先看步骤1:

Class.forName("com.mysql.cj.jdbc.Driver");

查看对应的com.mysql.cj.jdbc.Driver路径下的源码:

package com.mysql.cj.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!");         }     } }

是通过静态方法调用registerDriver()方法来将MySQL驱动注入到DriverManagerregisterDriver()方法具体如下:

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:

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

DriverInfo类中封装了java.sql.Driver接口:

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

再看步骤2、3,重点是步骤3

Connection connection = DriverManager.getConnection(url, username, password);

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

public interface Connection  extends Wrapper, AutoCloseable {      Statement createStatement() throws SQLException;     //... }

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

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接口的具体实现部分,MySQL的连接是通过两层实现完成抽象部分的实现:

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;     //... }

综上我们可以画出对应的类图:

设计模式学习笔记桥接模式及其应用

参考资料

http://c.biancheng.net/view/1364.html

https://jishuin.proginn.com/p/763bfbd68968

https://www.cnblogs.com/kuluo/p/13038076.html