本文参考

网上对于JDBC与桥接模式的理解各有不同,在这片文章里提出的是我个人对于二者的理解,本文参考的其它博文如下:

https://blog.csdn.net/paincupid/article/details/43614029

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

https://www.jianshu.com/p/775cb53a4da2

杨曙.基于设计模式之桥接模式的浅析[J].电脑知识与技术,2013,9(02):433-434+443.

桥接模式的定义与特点

  • 定义:

    将抽象与实现分离,使它们可以独立变化。它是用组合/聚合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。例如针对一个图形,我们可以设计颜色和形状两个变化维度

  • 优点:

    由于抽象与实现分离,所以扩展能力强;实现细节对客户透明

  • 缺点:

    由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度

桥接模式的基本结构

  • Abstraction — 抽象化角色:

    定义抽象的接口,包含一个对实现化角色的引用

  • Refined Abstraciotn — 扩展抽象化角色:

    抽象化角色的子类,实现父类中的业务方法,并通过组合/聚合关系调用实现化角色中的业务方法

  • Implementor — 实现化角色:

    定义具体行为、具体特征的应用接口,供扩展抽象化角色使用

  • Concrete Implemetor — 具体实现化角色:

    实现化角色的具体实现

  • 基本的模式结构类图如下:

桥接模式的应用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时

JDBC源码剖析

在不使用Spring、Hibernate等第三方库的情况下,直接通过原生JDBC API连接MySQL数据库,则有如下示例代码:

Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://<host>:<port>/<database>");

短短两行代码难以看出桥接模式的结构,下面先对源码进行一定的分析,理解各个类和接口之间的关系

  • Class.forName()方法

该方法将返回与给定字符串名的类或接口相关联的java.lang.Class类对象,用于在程序运行时的某个时刻,由客户端调用,动态加载该类或该接口到当前线程中

Returns the Class object associated with the class or interface with the given string name.
Given the fully qualified name for a class or interface this method attempts to locate, load, and link the class or interface.

若Class.forName()加载的是一个类,也会执行类中包含的static { } 静态代码段

  • com.mysql.cj.jdbc.Driver类

MySQL将具体的java.sql.Driver接口的实现放到了NonRegisteringDriver中,com.mysql.cj.jdbc.Driver类仅包含一段静态代码,具体类图如下:

其中最关键的是静态代码段中的 DriverManager.registerDriver(new Driver()) ,它会在客户端调用Class.forName()方法加载com.mysql.cj.jdbc.Driver类的同时被执行,Driver类自身的一个实例被注册到DriverManager(即保存到DriverManager的静态字段registeredDrivers内),注册过程的源码如下:

public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da)

throws SQLException {

  /* Register the driver if it has not already been added to our list */

  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接口的CopyOnWriteArrayList类,它能够保存进一步封装java.sql.Driver接口的DriverInfo类实例,DriverInfo类的声明代码如下:

class DriverInfo {

  final Driver driver;

  DriverAction da;

  DriverInfo(Driver driver, DriverAction action) {

    this.driver = driver;

    da = action;
  }
  // ……
}

引申:

DriverInfo还包装了DriverAction,DriverAction会在Driver被取消注册时被调用,DriverAction的源码注释如下:

The JDBC driver's static initialization block must call DriverManager.registerDriver(Driver, DriverAction) in order to inform DriverManager which DriverAction implementation to call when the JDBC driver is de-registered.

MySQL的Driver在向DriverManager进行注册时,DriverAction被设置为null

  • DriverManager类

由上面的分析可得,Class.forName()方法调用后,com.mysql.cj.jdbc.Driver类被加载,并执行static { } 静态代码段,将com.mysql.cj.jdbc.Driver类实例注册到DriverManager中。然后,客户端会调用DriverManager.getConnection()方法获取一个Connection数据库连接实例,该方法的部分源码如下:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
  // ……

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

DriverManager.getConnection()方法会遍历registeredDrivers静态字段,获取字段内保存的每一个Driver来尝试响应客户端的数据库连接请求,若所有Driver都连接数据库失败,则提示连接失败信息

  • Connection接口

Connection代表和特定数据库的连接会话,能够执行SQL语句并在连接的上下文中返回执行结果。

A connection (session) with a specific database. SQL statements are executed and results are returned within the context of a connection.

因此,DriverManager.getConnection()方法返回的Connection数据库连接实例根据不同的数据库有不同的实现,MySQL的Connection接口实现关系如下:

源码类图

根据源码的分析,绘制类图如下:

对Driver和Connection进行抽象,绘制类图如下:

模式体现

桥接模式通过组合/聚合关系代替继承关系,实现抽象化和实现化部分的解耦。以上述JDBC在MySQL中的简略类图为例,抽象化部分有Driver接口和Connection接口,实现化部分有DriverManager。对于不同的数据库,Driver接口和Connection接口都有自己独特的实现类

但是,和Driver接口不同的是,Connection接口与DriverManager类的关系只是联系较弱的依赖关系,并不符合桥接模式的定义和特点。因此,在考虑桥接模式的情况下,可以再次将类图进行简化:

最后,我们将其它数据库的Driver接口实现也考虑在内,绘制类图如下:

桥接模式中的实现化(Implementor)角色对应上图的Driver接口,具体实现化(Concrete Implementor)角色对应MysqlDriver、OracleDriver和MariadbDriver,扩展抽象化 (Refined Abstraction)角色对应DriverManager,不具有抽象化(Abstraction)角色作为扩展抽象化角色的父类

桥接模式的主要应用场景是某个类存在两个独立变化的维度,且这两个维度都需要进行扩展,而现在仅有Driver一个变化维度,DriverManager没有抽象化父类,它本身也没有任何子类,因此我认为,在JDBC中,是一种简化的桥接模式 —— 观点一

倘若JDBC针对Connection接口的设计不是将它作为Driver和DriverManager的"依赖"来处理,而是也作为一个变化的维度加入到桥接模式,或许能够更好地体现JDBC对桥接模式的实现,一种"假想"的桥接模式如下:

其它观点二:JDBC采用的是策略模式而不是桥接模式

https://www.zhihu.com/question/67735508

问题源自知乎,但是没有任何人做出解答,因为这确实和策略模式十分相似,如果把桥接模式的抽象部分简化来看,不去设计Abstraction,也就是用Refined Abstraction代替Abstraction,那么就类似于策略模式的Context来使用接口的对象

但是,桥接模式和策略模式的目的是不一样的,策略模式属于对象行为模式(描述对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责),它的目的是封装一系列的算法,使得算法可以相互替代,并在程序运行的不同时刻选择合适的算法。而桥接模式属于对象结构模式(描述如何将对象按某种布局组成更大的结构),它的目的是将抽象与实现分离,使它们可以独立变化

因此,从设计的目的来看,JDBC采用的并不是策略模式,在一段程序中数据库驱动并不存在频繁地相互替换

其它观点三:变化的维度一个是平台,另一个是数据库

https://www.unclewang.info/learn/java/771/?tdsourcetag=s_pctim_aiomsg

这是我认同的一个观点,引用原文的话

变的是平台和数据库,平台在jvm这个层面就解决了,因为所有操作系统java基本都会提供对应JDK,这也是"Once Write,Run AnyWhere"的原因。而数据库则是依托公司的具体实现,各个公司都提供对应的Driver类,我用DriverManager类进行懒加载

考虑数据库的实际应用场景,我们可能在不同的操作系统上使用不同的数据库,但是JVM的平台无关性使得我们不再有操作系统层面上的变化。假设不存在JVM,那么不同的客户端加载和运行数据库驱动程序的代码自然也各有不同,即DriverManager会因操作系统的变化而变化,不同的操作系统可以有不同的注册Driver的方式

不过因为存在JVM,我们现在不再有"平台"这一变化维度了

其它观点四:变化的维度一个是客户端应用系统,另一个是数据库

https://www.jianshu.com/p/775cb53a4da2

一个比较独特的观点,引用原文的话

应用系统作为一个等级结构,与JDBC驱动器这个等级结构是相对独立的,它们之间没有静态的强关联。应用系统通过委派与JDBC驱动器相互作用,这是一个桥梁模式的例子。

原文笔者不认为DriverManager作为Refined Abstraction角色存在,而是视作两个变化维度之间的一个"过渡",原本的"桥"是Abstraction和Implementor之间的组合/聚合关系,而现在DriverManager类本身成为了"桥",可以看作是桥梁模式的一个变体

新的观点五:变化的维度一个是Driver,一个是Connection

如果从观点四的原文笔者的角度看,把DriverManager类本身作为"桥",那么我们还可以提出一种新的观点,绘制类图如下:

JDBC和桥接模式的更多相关文章

  1. 桥接模式:探索JDBC的接口

    目录概要 场景问题 假设要设计一个电脑商场管理系统的某个模块设计,电脑分为品牌和类型两个纬度,我们应该怎么解决?我们初学者最容易想到的办法就是利用继承的方式,那利用继承实现的类图又是什么样子呢?我们看 ...

  2. java面试题之----jdbc中使用的设计模式(桥接模式)

    1.JDBC(JavaDatabase Connectivity) JDBC是以统一方式访问数据库的API. 它提供了独立于平台的数据库访问,也就是说,有了JDBC API,我们就不必为访问Oracl ...

  3. 桥接模式:探索JDBC底层实现

    一.目录概要 二.问题探究 需求:假设要设计一个电脑商场管理系统的某个模块设计,电脑分为品牌和类型两个纬度,我们应该怎么解决? 按照初学者的思路,利用继承就能简单粗暴的实现,那我们来看下这种思路的设计 ...

  4. 设计模式:桥接模式及代码示例、桥接模式在jdbc中的体现、注意事项

    0.背景 加入一个手机分为多种款式,不同款式分为不同品牌.这些详细分类下分别进行操作. 如果传统做法,需要将手机,分为不同的子类,再继续分,基本属于一个庞大的多叉树,然后每个叶子节点进行相同名称.但是 ...

  5. 桥接模式/bridge模式/对象结构型

    意图 将抽象部分与它的实现部分分离,使它们都可以独立的变化. 动机 当一个抽象类有多个实现时,通常用继承来协调它们.但是继承机制将抽象和实现固定,难以对抽象部分和实现部分独立地进行修改.扩充和重用. ...

  6. Java设计模式-桥接模式(Bridge)

    桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化.桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时 ...

  7. 桥接模式(Bridge Pattern)

    桥接模式,用于将抽象化与实现化解偶,使得二者可以独立变化. 举一个数据库JDBC的例子: 定义一个Driver接口,不同的数据库实现的接口,如MySQL,SQLServer public interf ...

  8. 设计模式入门之桥接模式Bridge

    Abstraction:抽象部分的父类,定义须要实现的接口.维护对实现部分的引用,从而把实现桥接到Implementor中去 Implementor:实现部分的接口 RefinedAbstractio ...

  9. java设计模式之十桥接模式(Bridge)

    桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化.桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时 ...

随机推荐

  1. 案例三:shell统计ip访问情况并分析访问日志

    题目要求 有日志 1.log,部分内容如下: 112.111.12.248 – [25/Sep/2013:16:08:31 +0800]formula-x.haotui.com"/secco ...

  2. Java刷题时常用的标准库数据结构和相应算法

    目录 一.线性表(广义的数组) 1. 数组 一维数组的定义和初始化 二维数组的定义和初始化 Arrays工具类的一些常用方法 2. List接口容器 对象的构建 读写和插入删除数据 排序 反转数组 二 ...

  3. C#内联函数 特性 MethodImplOptions.AggressiveInlining)

    [MethodImpl(MethodImplOptions.AggressiveInlining)] 内联函数 Impl:implement的缩写 内联函数 在计算机科学中,内联函数(有时称作在线函数 ...

  4. 通过xmanager连接linux远程主机桌面

    转至:https://blog.csdn.net/kadwf123/article/details/79564293 1.效果图: 远程linux桌面版主机,此处是虚拟机: 使用xmanager xb ...

  5. GAN实战笔记——第六章渐进式增长生成对抗网络(PGGAN)

    渐进式增长生成对抗网络(PGGAN) 使用 TensorFlow和 TensorFlow Hub( TFHUB)构建渐进式增长生成对抗网络( Progressive GAN, PGGAN或 PROGA ...

  6. JavaScript与C#互通的DES加解密算法的实现(转)

    本文提供了一个能使JavaScript与C#互通的DES加解密算法的实现,在前台页面中用JavaScript版本的DES算法将数据加密之后,传到服务器端,在服务器端可用C#版本的DES解密算法将其解密 ...

  7. VMware虚拟机三种网络模式

    VMware三种网络模式 在学习中经常遇到Vmware虚拟机网络设置有问题,可能是因为你没有理解这三种网络模式的工作原理.VMware虚拟机常见的网络类型有bridged(桥接).NAT(地址转换). ...

  8. Tableau怎么制作专业图表

    Tableau怎么制作专业图表 本文首发于博客冰山一树Sankey,去博客浏览效果更好.直接右上角搜索该标题即可 一. 统计表 1.1 不同种类的图表风格 商业周刊的图表风格 经济学人的图表风格 华尔 ...

  9. github通过token提交代码

    github通过token提交代码 生成token 个人头像->Setttings->Developer settings->Personal access tokens->G ...

  10. Ajax的疑难杂种详解

    跨域问题 如图所示,这是通过jquery封装的ajax请求了一个本地的php文件(无框架),console提示CORS策略已阻止"来自来源"的"null":请求 ...