细看JDBC——分工和桥接模式

原来看《Head First》的时候,在最后的附加上写过桥接模式,主要是从定义的角度入手,没有结合具体例子,正好上篇文章刚说过DriverManager怎么加载各种数据库的Driver,但没有具体讲JDBC,所以就想换些角度再看看JDBC。

JDBC定义

JDBC是Java DataBase Connectivity的缩写,即java数据库连接,是一种可以直接执行SQL语句的Java API。也就是说,只要是用标准的SQL语言去进行数据库的操作,都只要使用JDBC API根据不同的数据库,加载不同的数据库驱动程序,再进行操作就行了。

从上面这段话,要理解变与不变。

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

JDBC API和JDBC驱动程序分别干了什么

JDBC API要做的基本工作:

  1. 建立与数据库的连接
  2. 执行SQL语句
  3. 获得SQL语句的执行结果

JDBC驱动程序的工作:
负责将JDBC API中的调用映射成特定的数据库。

换个角度看他们俩的工作

上篇文章说过这个JVM设计者妥协(妥协的原因参考上篇文章)了和各种数据库厂商合作完成的。

就像人类历史的三次社会大分工一样,分工合作的原因可能来自征服自然的能力,可能是科技的发展、可能是集体意识的形成和归属感、可能是宗教等等,这是社会之所以发展成现代社会的原因,然后会有经济学、法学、管理学等,存在一定的必然性。分工合作的原因是不一样的,但是从社会这个整体的角度看,其节省成本,提高社会运转效率的结果是一定的。

不扯太远了,反正就是分工会提高效率,大家都舒服,而且效率高。

细看“分工”

上面已经说了很多了,现在的问题是他俩分的什么工?有必要分工吗?怎么分的工?

有必要分工?

上面好像说的很简单,但是试想一下:

  1. java想着自己同时全部实现各种数据库的连接以及SQL操作,像mysql、oracle大的我就不说了,java设计者自己实现也就算了,例如我国国产化的达梦数据库,要是你想用还得通知java设计者你帮忙给我实现了,根本不可行。
  2. 那数据库提供商我自己全部实现一套呢,这就是一个重复造轮子的问题,不说这个轮子造起来难不难,但是用起来就难了,你定义的是A方法,另外一家公司同样的目的是B方法,但大家都是使用标准SQL,没啥区别,谁都自己写一套,没有一个规范对于java的开发者来说,用起来太累。

分的什么工?

工作混在一起说,就是要做到每种数据库java使用各种标准SQL语句都能行。
所以就是各种数据库的适配和java执行标准SQL语句。
所以就要有具体的数据库适配的事情,另外就是适配了咱们怎么用的问题。
所以我就定义一套规范,适配各种事情我都考虑,最后接口具体要做的事情你来做。

怎么分的工?

上一篇文章其实已经讲过了,不过是从jvm角度看,现在从设计模式来看。
答案就是下面的桥接模式。

桥接模式

我原来也写过桥接模式,不过写的很简陋。这次要细看一下。

原来是这么写的

使用桥接模式不只改变你的实现,也改变你的抽象。
桥接模式通过将实现抽象放在两个不同的类层次中而使它们可以独立改变。
遥控器很多电视,遥控器要按照一定规则对电视进行控制。遥控器要改变,电视也会改变。

优点:

将实现予以解耦,让它和界面之间不再永久绑定。
抽象和实现可以独立扩展,不会影响到对方。
对于“具体的抽象类”所做的改变,不会影响到客户。

用途:

适合使用在需要跨越多个平台的图形和窗口系统上。
当需要用不同的方法改变接口和实现时,你会发现桥接模式很好用。

缺点:

桥接模式的缺点是增加了复杂度。

一个遥控器操纵各种电视

当然,你可以每个电视有一套规矩,然后有一个专门的遥控器,但是就像上面说的一样,分工一下,让一个遥控器可以控制所有电视,用户体验会更好。
单向控制,抽象的遥控器可以控制TV,TV不能选择被哪个遥控器操纵,这就是单向桥。

桥接模式的特殊之处

因为抽象部分可能具体要用到实现部分的功能,所以要搭个桥,让抽象部分可以通过这个桥去实用具体实现部分的相关操作。
比如上面的遥控器想实现“下个频道”,其实是需要要用具体的TV中“改变频道”这个方法的。

从桥接模式看JDBC

一方面看Driver接口和DriverManager的关系,DriverManager里的获取连接,Driver虽然在java里面是个接口,但在具体的驱动程序里面,Driver都已经实现好了,而且是按照预先约定好的接口来操作计算机系统或者是外围设备的程序的,所以可以利用registeredDrivers存取所有被AppClassLoader加载器加载的实现Driver接口的具体类,然后分别调用Drive自己实现的connect()方法,如果没有异常,就成功了。
另一方面是Connection接口和DrvierManager的关系,Connection定义了非常多的SQL基本操作方法的名字与返回类型,通常我们真正使用也是用Connection的实例去具体进行操作,这些操作也是具体的数据库实现的。

所以这里用了桥接模式,但不是简单的遥控器控制电视,那样在遥控器中放置一个抽象的声明,在构造方法中实例化。
而是使用DriverManager这个更加明显的桥,将实现了抽象操作SQL的Connection接口的实例和真正的Driver实例去利用registeredDrivers等数据结构对接起来,关键的Connection con = aDriver.driver.connect(url, info);也是体现出来实现和抽象对接的地方,而Connection可以单独改变,Driver的实现也可以根据不同的数据库具体实现。
抽象的Connect一直持有的是抽象的Driver,而不是已经实现的Driver,真正实现了抽象部分和实现部分相互独立的变化。

这样一来,几乎可以把所有面向抽象编写的程序,都视作是桥接模式的体现,至少算是简化的桥接模式。

何时选用桥接模式

建议在如下情况中,选用桥接模式

如果你不希望在抽象和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象和实现部分分开,然后在程序运行期间来动态的设置抽象部分需要用到的具体的实现,还可以动态切换具体的实现。

如果出现抽象部分和实现部分都应该可以扩展的情况,可以采用桥接模式,让抽象部分和实现部分可以独立的变化,从而可以灵活的进行单独扩展,而不是搅在一起,扩展一边会影响到另一边。

如果希望实现部分的修改,不会对客户产生影响,可以采用桥接模式,客户是面向抽象的接口在运行,实现部分的修改,可以独立于抽象部分,也就不会对客户产生影响了,也可以说对客户是透明的。

如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

感慨一下

  1. 接口的重要性,对于数据库的连接和具体操作,java本身什么也没实现,就是加载了具体的驱动,但是接口就像是规范,在计算机的海洋中是十分重要的。
  2. 对于数据库的连接和具体操作,java本身是没干啥,不过不要忘了java可是牺牲了另外一套自己的规范——双亲委派,这些操作仍然不是一两句桥接模式就能搞定的,要正确认识jvm和数据库提供商的关系

发表评论

电子邮件地址不会被公开。