设计模式-结构型-06-桥接模式

1、传统方式解决手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
在这里插入图片描述
UML 类图

在这里插入图片描述
问题分析

  1. 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
  2. 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  3. 解决方案——使用桥接模式

2、桥接模式基本介绍

  1. 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
  2. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
  3. 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

举个例子说明下:

对于手机来说,我们可以根据手机品牌分类,也可以根据手机类型来分类。所以手机这个系统可以在这两个角度独立的变化,手机品牌的变化不影响手机类型的变化。桥接模式其实就是通过合成/聚合代替继承,实现了松耦合的、在各个不同角度的”独立地变化“。

原理类图
在这里插入图片描述
原理类图说明

  • Client:桥接模式的调用者
  • Abstraction:Abstraction 充当桥接类,维护了 Implementor,即 ConcreteImplementorA / ConcreteImplementorB
  • RefinedAbstraction:Abstraction 抽象类的子类
  • Implementor:行为实现类的接口
  • ConcreteImplementorA / ConcreteImplementorB:行为的具体实现类
  • 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系

3、桥接模式解决手机操作问题

UML 类图
在这里插入图片描述
核心代码

// 行为接口——品牌接口
public interface Branch {void open();void call();void close();
}
// 行为实现类——华为品牌
public class Huawei implements Branch {@Overridepublic void open() {System.out.println("华为手机开机");}@Overridepublic void call() {System.out.println("华为手机打电话");}@Overridepublic void close() {System.out.println("华为手机关机");}
}
// 行为实现类——小米品牌
public class Xiaomi implements Branch {@Overridepublic void open() {System.out.println("小米手机开机");}@Overridepublic void call() {System.out.println("小米手机打电话");}@Overridepublic void close() {System.out.println("小米手机关机");}
}
// 行为实现类——苹果品牌
public class iPhone implements Branch {@Overridepublic void open() {System.out.println("苹果手机开机");}@Overridepublic void call() {System.out.println("苹果手机打电话");}@Overridepublic void close() {System.out.println("苹果手机关机");}
}// 桥接类——手机抽象类
public abstract class Phone {private Branch branch;public Phone(Branch branch) {this.branch = branch;}public void open() {branch.open();}public void call() {branch.call();}public void close() {branch.close();}
}
// 桥接子类——翻盖式手机
public class FlipPhone extends Phone {public FlipPhone(Branch branch) {super(branch);System.out.println("翻盖式手机");}@Overridepublic void open() {super.open();}@Overridepublic void call() {super.call();}@Overridepublic void close() {super.close();}
}
// 桥接子类——滑盖式手机
public class SlidePhone extends Phone {public SlidePhone(Branch branch) {super(branch);System.out.println("滑盖式手机");}@Overridepublic void open() {super.open();}@Overridepublic void call() {super.call();}@Overridepublic void close() {super.close();}
}
// 桥接子类——直立式手机
public class UprightPhone extends Phone {public UprightPhone(Branch branch) {super(branch);System.out.println("直立式手机");}@Overridepublic void open() {super.open();}@Overridepublic void call() {super.call();}@Overridepublic void close() {super.close();}
}public class Client {public static void main(String[] args) {Phone phone = new FlipPhone(new Huawei());phone.open();phone.call();phone.close();System.out.println("======================");phone = new FlipPhone(new Xiaomi());phone.open();phone.call();phone.close();System.out.println("======================");phone = new UprightPhone(new iPhone());phone.open();phone.call();phone.close();}
}

4、JDBC 源码分析

说起jdbc,我相信很多人都不陌生,在最开始的web项目中,我们常常用它来连接数据库执行sql语句,下面是一个连接mysql的例子:


/***    定义数据库连接辅助类*/
public class DBhelper {private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";private static final String URL = "jdbc:mysql://127.0.0.1:3306/test";private static final String USER = "root";private static final String PASSWORD = "123456";private Connection conn = null;private Statement st = null;private PreparedStatement ppst = null;private ResultSet rs = null;     /*加载驱动*/static {try {Class.forName(DRIVERNAME).newInstance();} catch (Exception e) {e.printStackTrace();System.out.println("加载驱动失败");}}     /*获取数据库连接*/public Connection getConn() {try {conn = DriverManager.getConnection(URL, USER, PASSWORD);} catch (SQLException throwables) {throwables.printStackTrace();System.out.println("获取数据库连接失败");}return conn;}     /*释放数据库连接*/public void releaseConn() {if (rs != null) {try {rs.close();} catch (SQLException e) {System.out.println(e.getMessage());}}if (st != null) {try {st.close();} catch (SQLException e) {System.out.println(e.getMessage());}}if (ppst != null) {try {ppst.close();} catch (SQLException e) {System.out.println(e.getMessage());}}if (conn != null) {try {conn.close();} catch (SQLException e) {System.out.println(e.getMessage());}}}

测试一下:

public class TestJdbc {private Connection conn = null;private Statement st = null;private PreparedStatement ppst = null;private ResultSet rs = null;private List<Object> selectUser(User user) {List<Object> list = new ArrayList<>();DBhelper dBhelper = new DBhelper();conn = dBhelper.getConn();String sql = "select * from blog_user where login_num=" + user.getLoginNum() + " and password =" + user.getPassword();try {st = conn.createStatement();rs = st.executeQuery(sql);ResultSetMetaData rsmd = rs.getMetaData();int columnCount = rsmd.getColumnCount();while (rs.next()) {Map map = new HashMap();for (int i = 1; i <= columnCount; i++) {map.put(rsmd.getColumnLabel(i), rs.getObject(i));}list.add(map);}} catch (SQLException throwables) {throwables.printStackTrace();} finally {dBhelper.releaseConn();}return list;}public static void main(String[] args) {TestJdbc testJdbc = new TestJdbc();User user = new User();user.setLoginNum(123456);user.setPassword("123456");List list = testJdbc.selectUser(user);}
}

debug一下:

在这里插入图片描述
然而在实际开发中,我们常常用到的框架是mybatis,其实mybatis就是对jdbc的封装,在我们以后的开发中我们可能会遇到关于持久层的各种问题,我们理解了jdbc的原理,那么mybatis又有何难?

依然以mysql为例,首先,我们来看数据库驱动Driver的加载过程:

在上面的贴出的代码中,我们可以看到一个静态代码块,利用class.forName()加载这个驱动,如下图:

  private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";/*加载驱动*/static {try {Class.forName(DRIVERNAME).newInstance();} catch (Exception e) {e.printStackTrace();System.out.println("加载驱动失败");}}

我们点进这个驱动,发现它继承了一个父类,实现了一个接口,里面有一个方法----注册驱动, 如下图:

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!");}}
}

我们先来看父接口 Driver,如下图:

这是一个顶层接口(由于太长,注释删掉一部分),简单理解一下注释,我们就会明白,这是一个所有的驱动必须实现的方法,也就是说,这是一个java连接数据库的一个接口,一个规范,数据库有很多种,因此数据库驱动也分很多种,但是不管你是那种数据库驱动,必须都要实现这个接口,遵循这个规范,才能连接数据库,无疑给我们带来了很大的方便,更换数据库就代表着更换驱动,而所有驱动都实现了这个接口,那我们只需要在利用反射加载驱动的class.forName()方法中注明需要加载的驱动就ok了,这样就可以适配所有的数据库.

/*** 每个驱动程序类必须实现的接口。*  <P>Java SQL 框架允许多个数据库驱动程序。 <P>* 每个驱动程序都应提供一个实现 * 驱动程序接口的类。* <P>DriverManager 将尝试加载尽可能多的驱动程序,然后对于任何给定的连接请求,* 它将依次要求每个 * 驱动程序尝试连接到目标 URL。 <P>* 强烈建议每个 Driver 类都应该是 * 小且独立的,以便 Driver 类可以加载和 * 查询,而无需引入大量支持代码。* <P>加载 Driver 类时,它应该创建 * 本身的实例并将其注册到 DriverManager。* 这意味着 * 用户可以通过调用以下命令来加载和注册驱动程序:* <p> * {@code Class.forName(“foo.bah.Driver”)}*/
public interface Driver {/*** 尝试与给定 URL 建立数据库连接。    ** 如果驱动程序意识到连接到给定 URL 的驱动程序类型错误,则应返回“null”。* 这很常见,因为当 * 要求 JDBC 驱动程序管理器连接到给定的 URL 时,* 它会依次将 * URL 传递给每个加载的驱动程序。    **/Connection connect(String url, java.util.Properties info) throws SQLException;/*** 检索驱动程序是否认为它可以打开与给定 URL 的连接 *。 * 通常,如果驱动程序 * 理解 URL 中指定的子协议,则返回 <code>true</code>,* 如果 * 不理解,则返回 <code>false</code>。*/boolean acceptsURL(String url) throws SQLException;DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;int getMajorVersion();int getMinorVersion();boolean jdbcCompliant();public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

再看NonRegisteringDriver

它实现了Driver,实际上,它是mysql驱动的一部分,里面的一些方法是关于连接mysql数据库的一些配置细节,根据一些连接属性创建一个真正连接数据库的网络通道

public class NonRegisteringDriver implements Driver {public static String getOSName() {return Constants.OS_NAME;}public static String getPlatform() {return Constants.OS_ARCH;}static int getMajorVersionInternal() {return StringUtils.safeIntParse("8");}static int getMinorVersionInternal() {return StringUtils.safeIntParse("0");}public NonRegisteringDriver() throws SQLException {}     //接收url,验证url的合法性public boolean acceptsURL(String url) throws SQLException {try {return ConnectionUrl.acceptsUrl(url);} catch (CJException var3) {throw SQLExceptionsMapping.translateException(var3);}}/*** 根据给定的URL和属性信息建立数据库连接。** @param url 数据库连接URL,用于指定连接的数据库类型和位置。* @param info 属性信息,包含登录数据库所需的用户名和密码等信息。* @return 返回与数据库建立的连接对象,如果无法建立连接则返回null。* @throws SQLException 如果建立连接过程中发生错误,则抛出SQLException。*/public Connection connect(String url, Properties info) throws SQLException {try {try {// 检查URL是否被当前驱动接受,如果不接受则直接返回null。if (!ConnectionUrl.acceptsUrl(url)) {return null;} else {// 根据URL和属性信息获取ConnectionUrl实例,用于解析连接URL并确定连接类型。//负载均衡式访问ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);// 根据连接类型创建并返回相应的连接对象。switch (conStr.getType()) {case SINGLE_CONNECTION:return ConnectionImpl.getInstance(conStr.getMainHost());case FAILOVER_CONNECTION:case FAILOVER_DNS_SRV_CONNECTION:return FailoverConnectionProxy.createProxyInstance(conStr);case LOADBALANCE_CONNECTION:case LOADBALANCE_DNS_SRV_CONNECTION:return LoadBalancedConnectionProxy.createProxyInstance(conStr);case REPLICATION_CONNECTION:case REPLICATION_DNS_SRV_CONNECTION:return ReplicationConnectionProxy.createProxyInstance(conStr);default:return null;}}} catch (UnsupportedConnectionStringException var5) {// 如果连接字符串不被支持,则返回null。return null;} catch (CJException var6) {// 如果在连接过程中发生CJException,则将其转换为SQLException并抛出。throw (UnableToConnectException) ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);}} catch (CJException var7) {// 如果发生CJException,则将其转换为SQLException并抛出。throw SQLExceptionsMapping.translateException(var7);}}public int getMajorVersion() {return getMajorVersionInternal();}public int getMinorVersion() {return getMinorVersionInternal();}/*** 获取驱动程序属性信息。* 该方法通过分析给定的URL和属性,构造驱动程序需要的属性信息。它主要用于配置连接到数据库所需的属性。** @param url 数据库连接URL,用于解析数据库类型、主机、端口、数据库名称等信息。* @param info 已经存在的属性信息,可能包含主机、端口、数据库名称、用户和密码等信息。* @return DriverPropertyInfo数组,包含所有必要的驱动程序属性信息。* @throws SQLException 如果解析URL或获取属性信息过程中出现错误,则抛出SQLException。*/public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {try {// 初始化数据库连接所需的各个属性String host = "";String port = "";String database = "";String user = "";String password = "";// 如果URL不为空,则尝试解析URL以获取更多配置信息if (!StringUtils.isNullOrEmpty(url)) {ConnectionUrl connStr = ConnectionUrl.getConnectionUrlInstance(url, info);// 如果是单个连接配置,提取主机信息if (connStr.getType() == Type.SINGLE_CONNECTION) {HostInfo hostInfo = connStr.getMainHost();// 将主机信息转换为Properties对象info = hostInfo.exposeAsProperties();}}// 从info对象中提取出各个属性值if (info != null) {host = info.getProperty(PropertyKey.HOST.getKeyName());port = info.getProperty(PropertyKey.PORT.getKeyName());database = info.getProperty(PropertyKey.DBNAME.getKeyName());user = info.getProperty(PropertyKey.USER.getKeyName());password = info.getProperty(PropertyKey.PASSWORD.getKeyName());}// 创建驱动程序属性信息对象,并设置相应的属性和描述DriverPropertyInfo hostProp = new DriverPropertyInfo(PropertyKey.HOST.getKeyName(), host);hostProp.required = true;hostProp.description = Messages.getString("NonRegisteringDriver.3");DriverPropertyInfo portProp = new DriverPropertyInfo(PropertyKey.PORT.getKeyName(), port);portProp.required = false;portProp.description = Messages.getString("NonRegisteringDriver.7");DriverPropertyInfo dbProp = new DriverPropertyInfo(PropertyKey.DBNAME.getKeyName(), database);dbProp.required = false;dbProp.description = Messages.getString("NonRegisteringDriver.10");DriverPropertyInfo userProp = new DriverPropertyInfo(PropertyKey.USER.getKeyName(), user);userProp.required = true;userProp.description = Messages.getString("NonRegisteringDriver.13");DriverPropertyInfo passwordProp = new DriverPropertyInfo(PropertyKey.PASSWORD.getKeyName(), password);passwordProp.required = true;passwordProp.description = Messages.getString("NonRegisteringDriver.16");// 初始化JDBC属性集,并根据当前配置初始化属性JdbcPropertySet propSet = new JdbcPropertySetImpl();propSet.initializeProperties(info);// 将JDBC属性集暴露为驱动程序属性信息List<DriverPropertyInfo> driverPropInfo = propSet.exposeAsDriverPropertyInfo();// 创建一个包含所有属性信息的数组DriverPropertyInfo[] dpi = new DriverPropertyInfo[5 + driverPropInfo.size()];// 将基本属性和额外的驱动程序属性信息添加到数组中dpi[0] = hostProp;dpi[1] = portProp;dpi[2] = dbProp;dpi[3] = userProp;dpi[4] = passwordProp;System.arraycopy(driverPropInfo.toArray(new DriverPropertyInfo[0]), 0, dpi, 5, driverPropInfo.size());// 返回包含所有属性信息的数组return dpi;} catch (CJException var17) {// 将内部CJException转换为SQLException并抛出throw SQLExceptionsMapping.translateException(var17);}}public boolean jdbcCompliant() {return false;}public Logger getParentLogger() throws SQLFeatureNotSupportedException {throw new SQLFeatureNotSupportedException();}static {try {Class.forName(AbandonedConnectionCleanupThread.class.getName());} catch (ClassNotFoundException var1) {}}
}

然后就是 DriverManager.registerDriver( new Driver() ) 这个注册驱动的方法,它是将自己传给DriverManager,我们点开这个方法:
这里将Driver封装进DriverInfo类中,添加在DriverManager的静态List中,便于DriverManager管理驱动

 /*** class DriverManager* 这段代码定义了一个私有的、静态的、常量registeredDrivers,其类型为CopyOnWriteArrayList<DriverInfo>。这个列表用于存储DriverInfo类型的元素。* CopyOnWriteArrayList是Java并发编程中的一种线程安全的列表实现。它通过使用“写时复制”(Copy-on-Write)的策略来实现并发访问和修改。当有线程尝试修改列表时,会创建该列表的一个副本,并在副本上进行修改操作,而原列表则保持不变。这样可以确保在并发环境下,读操作的高效性和线程安全性。* 在该代码中,registeredDrivers列表用于存储DriverInfo类型的元素,可以进行元素的添加、删除和查询等操作。由于使用了CopyOnWriteArrayList,因此在并发环境下对列表的操作是线程安全的。*/private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {registerDriver(driver, null);}/*** 注册驱动程序* 该函数用于向DriverManager注册给定的 JDBC 驱动程序。* 如果驱动程序已经注册,则不会采取任何行动。该方法接受两个参数:* driver是要注册的 JDBC 驱动程序,* da是当调用DriverManager#deregisterDriver时使用的DriverAction实现。* 如果driver为null,则会抛出NullPointerException。* 如果注册成功,则会在控制台打印registerDriver:加上驱动程序的信息。* @param driver* @param da* @throws SQLException*/public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {/* 如果驱动程序尚未添加到我们的列表中,请注册它 */if (driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver, da));} else {//这是为了与原始 DriverManager 兼容throw new NullPointerException();}println("registerDriver: " + driver);}

再点开: registeredDrivers.addIfAbsent(new DriverInfo(driver, da))
方法名大概是说 如果缺席(没有)就添加类里面有一个array,看注释,这是一个放置驱动类的临时数组,只能通过getArraysetArray获取和设置.

indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot)是返回参数e 在数组snapshot 的下标,这里的 e 就是 上面的new DriverInfo(driver, da), snapshot 为上面提到的 array
当e为null,返回snapshot中null的下标.如果snapshot中没有e,则返回 -1,也就是说其实是判断snapshot中有没有e,没有的话,就调用方法添加.

再看下面的添加方法,就是把 Driver 放进 array 中,相当于把驱动注册进DriverManager中,至于这里为什么是一个数组?假如我们一个系统同时连接两种或者多种数据库,那我们就需要多个驱动,因此这里是一个数组,当我们需要连接哪种数据库的时候,就可以从这里取出对应的驱动去获取连接

private transient volatile Object[] array;/*** 查找元素在数组中的索引。** @param o 要查找的元素,可以为null。* @param elements 目标数组,可能包含null元素。* @param index 搜索的起始索引。* @param fence 搜索的结束界限,但不包括该索引本身。* @return 如果找到元素,返回其索引;如果未找到,返回-1。** 方法首先检查要查找的元素是否为null,然后遍历数组从指定索引开始直到指定的界限。* 如果元素为null,则查找数组中的null元素;如果元素不为null,则使用equals方法进行匹配查找。* 这种方法允许在数组中高效地查找特定元素,无论元素是否为null。*/private static int indexOf(Object o, Object[] elements, int index, int fence) {if (o == null) {// 如果要查找的元素为null,则在数组中查找null元素。for (int i = index; i < fence; i++) if (elements[i] == null) return i;} else {// 如果要查找的元素不为null,则使用equals方法进行匹配查找。for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i;}// 如果未找到匹配的元素,返回-1。return -1;}/*** 在集合中添加元素e,仅当e不存在于集合中时添加。** @param e 要添加到集合中的元素。* @return 如果元素已存在,则返回false;如果元素成功添加,则返回true。*/public boolean addIfAbsent(E e) {// 获取当前集合的元素数组快照,用于后续判断元素是否已存在Object[] snapshot = getArray();// 判断元素e是否已存在于集合中if (indexOf(e, snapshot, 0, snapshot.length) >= 0) {return false; // 元素已存在,不添加,返回false} else {// 元素不存在,调用addIfAbsent方法实际添加元素return addIfAbsent(e, snapshot);}}/*** 该函数在同步锁定的情况下,检查给定的元素e是否存在于数组中。* 如果不存在,则将e添加到数组中,并返回true;* 如果存在,则返回false。函数首先比较给定的快照数组snapshot和当前数组是否相等,* 如果不相等,则遍历两个数组中长度较小的部分,* 检查e是否已经存在。如果e不存在,则继续查找剩余部分。* 如果e仍然不存在,则创建一个新数组,将e添加到新数组末尾,* 然后将新数组设置为当前数组。最后返回true表示成功添加元素。** @param e 要添加到集合的元素。* @param snapshot 快照数组,用于比较以确定元素是否已经存在。* @return 如果元素成功添加到集合中,则返回true;如果元素已经存在,则返回false。*/private boolean addIfAbsent(E e, Object[] snapshot) {final ReentrantLock lock = this.lock;lock.lock(); // 获取锁以确保线程安全try {Object[] current = getArray(); // 获取当前的元素数组int len = current.length; // 获取当前数组的长度if (snapshot != current) { // 检查快照数组是否是当前的元素数组// 针对另一个 addXXX 操作的失败竞争进行了优化              int common = Math.min(snapshot.length, len); // 计算快照数组和当前数组的长度的较小值for (int i = 0; i < common; i++) {if (current[i] != snapshot[i] && eq(e, current[i])) {return false; // 如果在相同位置上元素不相同且新元素已存在,则返回false}}if (indexOf(e, current, common, len) >= 0) {return false; // 如果新元素在当前数组的剩余部分中存在,则返回false}}Object[] newElements = Arrays.copyOf(current, len + 1); // 创建一个新数组,长度为当前数组长度加一newElements[len] = e; // 将新元素添加到新数组的末尾setArray(newElements); // 设置新数组为当前的元素数组return true; // 返回true,表示新元素已成功添加} finally {lock.unlock(); // 释放锁}}

我们再看如何获取连接Connection, DriverManager遍历其中的所有的驱动,然后获取该驱动的连接,这种方法或许有点笨,但是可以兼容所有的数据库驱动,而这里真正连接数据库的操作Connection con = aDriver.driver.connect(url, info);调用的是NonRegisteringDriver中的connect()方法,返回一个Connection实例

 /*** 根据给定的URL、属性和调用者类加载器获取一个数据库连接。* 此方法是getConnection方法的实现,它尝试通过已注册的驱动程序来建立连接。* * @param url 数据库连接URL,不能为null。* @param info 连接属性,可以为null。* @param caller 调用者的类,用于获取类加载器,可以为null。* @return 数据库连接对象。* @throws SQLException 如果无法建立连接或URL为null。*///  Worker method called by the public getConnection() methods.   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();}}// 检查URL是否为nullif (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) {// 记录第一个发生的SQLExceptionif (reason == null) {reason = ex;}}} else {// 跳过不被允许的驱动程序println("    skipping: " + aDriver.getClass().getName());}}// 如果有SQLException发生,抛出该异常if (reason != null) {println("getConnection failed: " + reason);throw reason;}// 如果没有找到合适的驱动程序,抛出SQLExceptionprintln("getConnection: no suitable driver found for " + url);throw new SQLException("No suitable driver found for " + url, "08001");}

再来看返回的连接 Connection,这也是一个接口,定义了一些数据库连接都要有的方法会用到的方法,我们看一下它的继承类图,如下:

package java.sql;import java.util.Properties;
import java.util.concurrent.Executor;/*** <P>与特定 * 数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果 *。* * <pre> * java.util.Map map = con.getTypeMap(); * * map.put(“mySchemaName.ATHLETES”, Class.forName(“运动员”)); * * con.setTypeMap(地图); * </pre> * * * @see DriverManager#getConnection * * @see语句 * * @see ResultSet * @see DatabaseMetaData*/
public interface Connection extends Wrapper, AutoCloseable {//所有的数据库连接都要有的方法     Statement createStatement() throws SQLException;PreparedStatement prepareStatement(String sql) throws SQLException;CallableStatement prepareCall(String sql) throws SQLException;String nativeSQL(String sql) throws SQLException;void setAutoCommit(boolean autoCommit) throws SQLException;boolean getAutoCommit() throws SQLException;void commit() throws SQLException;void rollback() throws SQLException;void close() throws SQLException;boolean isClosed() throws SQLException;
}public interface MysqlConnection {    //略    // 关于mysql连接的一些方法
}public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {//略
}public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {//略
}

根据上面的分析,如图所示:
在这里插入图片描述

JDBC 的 Driver 接口:如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver、Oracle 的 Driver,这些就可以当做实现接口类
在这里插入图片描述

在这里插入图片描述
Connection 继承体系

在这里插入图片描述

Driver源码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}public Driver() throws SQLException {// Required for Class.forName().newInstance()}
}

DriverManager 结构

在这里插入图片描述
说明

  • MySQL 有自己的 Connectionlmpl 类,同样 Oracle 也有对应的实现类
  • Driver 和 Connection 之间是通过 DriverManager 类进行桥连接的

5、注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景

6、桥接模式其他应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

常见的应用场景

  1. JDBC 驱动程序
  2. 银行转账系统
    • 转账分类:网上转账、柜台转账、AMT 转账
    • 转账用户类型:普通用户、银卡用户、金卡用户
  3. 消息管理
    • 消息类型:即时消息、延时消息
    • 消息分类:手机短信、邮件消息、QQ消息…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/29896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

shell脚本内使用ifconfig命令

脚本在定时任务中没有取出ip&#xff0c;说明ifconfig命令没有执行成功&#xff0c;就是没有找到ifconfig&#xff0c;与PATH环境变量有关&#xff0c;PATH环境变量在/etc/profile文件中设置&#xff0c;而定时任务却是以nologin方式调用脚本&#xff0c;不会加载/etc/profile,…

探讨 MyBatis 特殊 SQL 执行技巧与注意事项

在线工具站 推荐一个程序员在线工具站&#xff1a;程序员常用工具&#xff08;http://cxytools.com&#xff09;&#xff0c;有时间戳、JSON格式化、文本对比、HASH生成、UUID生成等常用工具&#xff0c;效率加倍嘎嘎好用。 程序员资料站 推荐一个程序员编程资料站&#xff1a;…

Zygote进程的理解

Zygote进程是安卓系统的一个重要进程&#xff0c;由init进程创建而来&#xff1b;另外系统里的重要进程&#xff08;system_server等&#xff09;都是由zygote进程fork的&#xff0c;所有的app进程也是由zygote进程fork的。 一、C 里的fork函数 fork是Linux里面创建子进程的函…

电池荷电状态估计SOC?电池管理系统

一、背景 电池荷电状态&#xff08;SOC, State of Charge&#xff09;估计是电池管理系统&#xff08;BMS, Battery Management System&#xff09;的关键功能之一&#xff0c;对于确保电池的安全高效运行至关重要&#xff0c;特别是在电动车、储能系统以及便携式电子设备等领…

LCL滤波器并网逆变器双闭环控制系统仿真

并网逆变器通常采用L滤波器&#xff0c;虽然结构和控制简单&#xff0c;但是随着功率级别的增加&#xff0c;体积重量增大等问题也日益突出。为了解决这个问题&#xff0c;人们开始使用LCL滤波器&#xff0c;这种滤波器在功率较大的场合表现出色。 无源滤波器&#xff0c;又称…

Android使用data uri启动activity或service

设定AndroidManifest.xml 在AndroidManifest.xml文件中&#xff0c;我们可以设定activity或service的data。 <!-- activity定义方式 --> <activityandroid:name".page.main.MainActivity"><intent-filter><action android:name"an…

神经网络模型---ResNet

一、ResNet 1.导入包 import tensorflow as tf from tensorflow.keras import layers, models, datasets, optimizersoptimizers是用于更新模型参数以最小化损失函数的算法 2.加载数据集、归一化、转为独热编码的内容一致 3.增加颜色通道 train_images train_images[...,…

QML语法

文章目录 QML属性 QML QML是一种描述用户界面的声明式语言。它将用户界面分解成一些更小的元素&#xff0c;这 些元素能够结合成一个组件。QML语言描述了用户界面元素的形状和行为。用户界 面能够使用JavaScript来提供修饰&#xff0c;或者增加更加复杂的逻辑。从这个角度来看…

Linux 系统图像化编程GTK入门

环境前期准备 演示环境&#xff1a;Windows 11 Ubuntu 22.04.4 VS Code 前提条件&#xff1a;1、Windows 11 子系统Ubuntu 22.04.4 已经安装图形化界面&#xff0c;如果没有安装请参考文章&#xff1a; windows11子系统Ubuntu 22.04.4子安装图形化界面 2、Ubuntu 22.04.4…

C语言笔记第16篇:编译和链接

1、翻译环境和运行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行机器指令&#xff08;二进制指令&#xff09; 第2种是执行环境&#xff0c;它用于实际执行代码 2、翻译环境 那翻译环境是怎…

数据资产管理的未来趋势:洞察技术前沿,探讨数据资产管理在云计算、大数据、区块链等新技术下的发展趋势

一、引言 随着信息技术的飞速发展&#xff0c;数据已成为企业最重要的资产之一。数据资产管理作为企业核心竞争力的关键组成部分&#xff0c;其发展趋势和技术创新受到了广泛关注。特别是在云计算、大数据、区块链等新技术不断涌现的背景下&#xff0c;数据资产管理面临着前所…

通过文章id递归查询所有评论(xml)

<!-- 通过文章id递归查询所有评论 --> <select id"findByArticleId" resultMap"commentResultMap">SELECT * FROM mxg_comment WHERE parent_id -1AND article_id #{articleId}ORDER BY create_date DESC </select><!-- 将每…

【凤凰房产-注册安全分析报告-缺少轨迹的滑动条】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

2024 端午节巽寮湾游玩记录

2024 端午节巽寮湾游玩记录 文章目录 2024 端午节巽寮湾游玩记录一、前言二、巽寮湾游玩行程1、三天衣食住行2、主要图片&#xff1a; 三、其他1、小结2、巽寮湾游玩建议3、感慨 一、前言 时间总是过得很快&#xff0c;只要你活着时间就会不停往前走。 所以你以后的路其实都是…

外卖APP开发详解:从同城O2O系统源码开始

近期&#xff0c;从事软件开发的小伙伴们都在讨论外卖APP&#xff0c;热度非常之高&#xff0c;所以小编今天将与大家一同探讨同城O2O系统源码、外卖APP开发。 一、外卖APP开发的前期准备 了解目标用户的需求&#xff0c;分析竞争对手的优劣势&#xff0c;明确自身的市场定位。…

如何在Vue3中处理异步API调用并更新表单数据(附Demo)

目录 前言1. 问题所示2. 知识分析3. 实战 前言 从实战问题中剖析知识点 1. 问题所示 执行Vue3数据的时候&#xff0c;终端输出的data如下所示 Promise {<pending>} [[Prototype]] : Promise [[PromiseState]] : "fulfilled" [[PromiseResult]] : Array(…

Paddleocr数据增强调用逻辑

数据增强调用逻辑 以在ppocr/data/simple_dataset.py为例&#xff1a; get_ext_data通过self.ops[:self.ext_op_transform_idx]获取配置文件中数据增强 self.ops在def __init__(self, config, mode, logger, seedNone):中通过解析配置文件中transforms内容获取数据增强操作&a…

ADB->获取当前正在显示的Fragment和Activity的ADB命令

获取当前显示的Activity adb shell "dumpsys window | grep mCurrentFocus"指令拆解adb shell&#xff1a;启动一个远程shell来运行设备上的命令dumpsys window&#xff1a;获取当前窗口管理器的信息|&#xff1a;将前一个命令的输出作为后一个命令的输入grep mCurr…

【Unity拖拽物体】实现对点中的3D物体进行拖拽的功能

场景结构&#xff0c;两个普通模型 第一种 脚本所挂载的物体才可以被拖拽 【PC鼠标版本】 using UnityEngine;// 这个脚本实现了&#xff0c;本脚本所在的游戏物体能够被拖拽 public class DragObjectT : MonoBehaviour {private Vector3 screenPoint; // 存储物体在屏幕上的位…

python GUI开发: tkinter菜单创建,记事本和画图软件综合项目的实战演练

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…