设计模式-结构型-06-桥接模式
1、传统方式解决手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
UML 类图
问题分析
- 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
- 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
- 解决方案——使用桥接模式
2、桥接模式基本介绍
- 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
- 它的主要特点是把抽象(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,看注释,这是一个放置驱动类的临时数组,只能通过getArray
和setArray
获取和设置.
而 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、注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景
6、桥接模式其他应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
常见的应用场景
- JDBC 驱动程序
- 银行转账系统
- 转账分类:网上转账、柜台转账、AMT 转账
- 转账用户类型:普通用户、银卡用户、金卡用户
- 消息管理
- 消息类型:即时消息、延时消息
- 消息分类:手机短信、邮件消息、QQ消息…
相关文章:

设计模式-结构型-06-桥接模式
1、传统方式解决手机操作问题 现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图: UML 类图 问题分析 扩展性问题(类爆炸):如果我们再…...

安泰电压放大器的选型原则是什么
电压放大器是电子电路中常用的一种器件,主要用于放大输入电压信号。在选型电压放大器时,需要考虑以下几个原则。 根据应用需求确定放大倍数。放大倍数是指输出电压与输入电压之间的倍数关系,也称为增益。不同的应用场景对放大倍数的要求不同&…...

方法分享 |公网IP怎么指定非433端口实现https访问
公网IP可以通过指定非443端口实现HTTPS访问。在网络配置中,虽然HTTPS协议默认使用443端口,但没有规定不能在其他端口上实施HTTPS服务。使用非标准端口进行HTTPS通信需要正确配置服务器和SSL证书,并确保客户端能够连接到指定的端口。下面说明如…...

vue实现拖拽元素;vuedraggable拖拽插件
效果图: 中文文档 以下代码可直接复制使用 安装依赖 npm i -S vuedraggable使用 <template><div class"container"><div>使用flex竖轴布局 <br>handle".mover" 可拖拽的class类名 <br>filter".forbid&qu…...

Javascript介绍
Javascript 定义:一门简单的浏览器可解析的语言 作用:与HTML相结合使用,使我们的网页变得更加酷炫 发展史: 1.1992年,Nombase公司开发出来,校验表单,起名c--,后来更名为&#…...

毕业答辩PPT:如何在短时间内高效准备?
提起PPT,大家的第一反应就是痛苦。经常接触PPT的学生党和打工人,光看到这3个字母,就已经开始头痛了: 1、PPT内容框架与文案挑战重重,任务艰巨,耗费大量精力。 2、PPT的排版技能要求高,并非易事…...

树结构与算法-杨辉三角形的两种实现
什么是杨辉三角形 本文旨在讨论普通杨辉三角形的两种实现方式:迭代法和递归法。我们不详细讲解杨辉三角形的数学问题,只研究其代码实现。 杨辉三角形大致如下图: 杨辉三角形的规律 通过对杨辉三角形的图形分析,我们可以看到这几点…...

【机器学习】智能创意工厂:机器学习驱动的AIGC,打造未来内容新生态
🚀时空传送门 🔍机器学习在AIGC中的核心技术📕深度学习🎈生成对抗网络(GANs) 🚀机器学习在AIGC中的具体应用🍀图像生成与编辑⭐文本生成与对话系统🌠音频生成与语音合成 …...

Python - 一个恶意脚本
Python - 恶意脚本 使用此脚本或修改前请注意以下几点: 系统资源:大量模拟键盘和鼠标事件可能会占用大量系统资源,会导致其他应用程序运行缓慢或崩溃。 隐私和安全:如果此脚本在未经用户同意的情况下运行,它可能侵犯…...

SFNC —— 采集控制(四)
系列文章目录 SFNC —— 标准特征命名约定(一) SFNC —— 设备控制(二) SFNC —— 图像格式控制(三) SFNC —— 采集控制(四) 文章目录 系列文章目录5、采集控制(Acquisi…...

AUTOSAR学习
文章目录 前言1. 什么是autosar?1.1 AP(自适应平台autosar)1.2 CP(经典平台autosar)1.3 我的疑问 2. 为什么会有autosar3.autosar的架构3.1 CP的架构3.1.1 应用软件层3.1.2 运行时环境3.1.3 基础软件层 3.2 AP的架构 4. 参考资料 …...

区区微服务,何足挂齿?
背景 睿哥前天吩咐我去了解一下微服务,我本来想周末看的,结果周末没带电脑,所以只能周一看了。刚刚我就去慕课网看了相关的视频,然后写一篇文章总结一下。这篇文章算是基础理论版,等我之后进行更多的实践,…...

数据结构 ->反转链表
工作原理 初始化: cur 指向传入的节点 node,即链表的头节点。prv 初始化为 NULL,用于存储当前节点的前一个节点。 循环反转: 在 while 循环中,当 cur 不为空时执行循环体。保存当前节点的下一个节点:使用 t…...

Unity基础(一)unity的下载与安装
目录 一:下载与安装 1.官网下载地址 2.推荐直接下载UnityHub 3.选择编辑器版本(推荐长期支持版) 4.在UnityHub安装选择相应的模块 二:创建项目 简介: Unity 是一款广泛应用的跨平台游戏开发引擎。 它具有以下显著特点: 强大的跨平台能力:能将开发的游…...

TOP10!YashanDB斩获广东省优秀信创产品与解决方案双料荣誉
近日,2024广东软件风云榜结果出炉,表彰为广东软件产业和数字经济、新型工业化发展作出突出贡献的企业、企业家、优秀产品等。深算院崖山数据库系统 YashanDB荣获广东省“2024年优秀信息技术应用创新产品TOP10”和“2024年优秀信息技术应用创新行业应用解…...

基于深度学习网络的USB摄像头实时视频采集与手势检测识别matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 系统架构 4.2 GoogLeNet网络简介 4.3 手势检测 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 训练过程如下: 将摄像头对准手势,然后进行…...

有趣且重要的JS知识合集(22)树相关的算法
0、举例:树形结构原始数据 1、序列化树形结构 /*** 平铺序列化树形结构* param tree 树形结构* param result 转化后一维数组* returns Array<TreeNode>*/ export function flattenTree(tree, result []) {if (tree.length 0) {return result}for (const …...

使用 Let’s Encrypt 生成免费 SSL 证书
使用 Let’s Encrypt 生成证书是一个简单且免费的方式,可以通过 Certbot 工具来实现。以下是详细的步骤说明: 1. 安装 Certbot 根据你的操作系统,安装 Certbot。以下以 Ubuntu 为例: sudo apt update sudo apt install certbot…...

【电脑小白】装机从认识电脑部件开始
前言 在 B 站上刷到了一个很牛逼的电脑装机视频,很适合电脑小白学习,故用文本记录下。 推荐对组装台式电脑有兴趣的小伙伴都去看看这个视频: 原视频链接:【装机教程】全网最好的装机教程,没有之一_哔哩哔哩_bilibil…...

ssldump一键分析网络流量(KALI工具系列二十二)
目录 1、KALI LINUX 简介 2、ssldump工具简介 3、在KALI中使用ssldump 3.1 目标主机IP(win) 3.2 KALI的IP 4、操作示例 4.1 监听指定网卡 4.2 指定端口 4.3 特定主机 4.4 解码文件 4.5 显示对话摘要 4.6 显示加密数据(需要私钥&…...

使用seq2seq架构实现英译法
seq2seq介绍 模型架构: Seq2Seq(Sequence-to-Sequence)模型是一种在自然语言处理(NLP)中广泛应用的架构,其核心思想是将一个序列作为输入,并输出另一个序列。这种模型特别适用于机器翻译、聊天…...

攻防演练“轻装上阵” | 亚信安全信舱ForCloud 打造全栈防护新策略
网络世界攻防实战中,攻击风险已经从代码到云横跨全栈技术点,你准备好了吗 云服务器,攻击众矢之的 2022年超过38万个Kubernetes API服务器暴露公网,成为攻击者目标。云服务器,尤其是开源设施,一直以来不仅是…...

在Android Studio中将某个文件移出Git版本管理
最新在整理代码时发现,local.properties文件开头有这么一段注释: ## This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. 大意是这个文件不要加入到版本管理中。 之…...

Vue46-render函数
一、非单文件和单文件的main.js对比 1-1、非单文件的main.js 1-2、 单文件的main.js 将单文件的main.js中的render函数变成非单文件的main.js中的template形式,报如下错误: 解决方式: 二、解决方式 2-1、引入完成版的vue.js 精简版的vue&a…...

@RequestParam 和 @PathVariable @Param注解的区别和作用
在Spring MVC中,RequestParam、PathVariable和 RequestBody 是用于处理不同类型的请求参数的注解。每个注解都有其特定的用途和用法。让我们分别看一下它们的区别和作用。 RequestParam RequestParam用于从请求参数中获取数据,通常是处理表单数据或URL…...

复习一下。
名词解释 数字图像:数字图像是通过数字技术捕获存储和处理的图像。它由一个矩阵或二维数组的像素组成,每个像素包含图像在该位置上的颜色或亮度信息。 像素:像素是构成数字图像的最小单位。每个像素代表图像中某个位置的颜色或亮度值。 分辨…...

ripro主题如何使用memcached来加速
ripro主题是个很不错的资源付费下载主题。主题自带了缓存加速开关,只要开启了缓存加速功能,正常情况下能让网站访问的速度提升很大。 但好多人这么做了却发现没啥加速效果,原因就在于wordpress里缺少了memcache文件。只需要把object-cache.ph…...

《珊瑚岛》是一款什么类型的游戏 苹果电脑如何玩到《珊瑚岛》
在众多电子游戏中,有些游戏因其独特的游戏体验和丰富的内容而脱颖而出,《珊瑚岛》便是其中之一。在游戏中你将离开宝京前往珊瑚岛,种植农作物、饲养动物、和岛民成为朋友。您不仅可以振兴该岛小镇,还可以保护和修复周围的珊瑚礁。…...

Go - 3.库源码文件
目录 一.引言 二.库源码文件 1.定义 2.生成库源码文件 3.直接调用库源码文件 三.总结 一.引言 前面我们学习了 命令源码文件,并成功运行了 go 的 hello world 代码,下面我们介绍 go 里面另一个概念: 库源码文件。 二.库源码文件 1.定义 库源码文…...

FPGA的基础仿真项目--七段数码管设计显示学号
一、设计实验目的 1. 了解数码管显示模块的工作原理。 2. 熟悉VHDL 硬件描述语言及自顶向下的设计思想。 3. 掌握利用FPGA设计6位数码管扫描显示驱动电路的方法。 二、实验设备 1. PC机 2.Cyclone IV FPGA开发板 三、扫描原理 下图所…...