Bridge(桥接模式)

    Bridge模式着重考虑如何实现对具体实现的抽象(abstraction)."抽象"一词用来指代依赖一系列抽象方法的类。这些抽象方法可以有多种不同的实现。这些抽象方法可以有多种不同的实现。

    实现对具体实现的抽象的一般做法是创建一个类层次结构:顶层的抽象类定义出各种抽象方法;每个子类给出这些抽象方法的不同实现。当我们出于其他原因需要对该类层次结构进行继承的时候,这种做法就显得不足了。

    我们可以使用Bridge模式将抽象方法移至接口中,这样所做的抽象将依赖该接口的实现。

    Bridge模式的意图是将抽象与抽象方法的实现相分离,这样它们就可以独自变化

    注:下面出现的所有StartController都应该是StarPressController.这是个小问题。

1. 经典范例:普通抽象

    假定我们使用机器控制类和Oozinoz公司生产焰火的机器进行交互。这些类在怎样操作机器方面会有所不同。然而,我们可能需要创建一些抽象的行为来使得对于任何机器都具有相同的效果。图1描述了com.oozinoz.controller包中当前的控制类。


                    
                         图1  图中显示的这两个类具有类似的方法,可以将其抽象为操作机器的通用模型

图6-1中的两个控制类都有启动和停止它们所控制的机器的方法。StartController类给那些方法命名为start()和stop()然而fuserController类即将那些方法命名为startMachine()和stopMachine()。这两个控制类都有将材料箱移至加工区(index()和converyIn())、启动和停止处理材料箱以及移走材料箱(discharge()和converyOut())的处理方法。FuserController类还有一个用来切换使用备用引线线圈的方法。

      现在假定我们需要创建一个shutdown()方法,在两台机器上执行同样的步骤,以确保有序地关机。为了简化shutdown()函数的编写,我们需要标准化那些常用的操作方法的名字,比如startMachine()、stopMachine()、startProcess()、stopProcess()、converyIn()、conveyOut()。但是这将导致我们不能改变那些控制类,因为其中的某些操作是机器所提供的。

自我突破题1  请描述如何使用设计模式通过一个公共接口来控制不同的机器?

答:为了使用公共接口来控制各种机器,可以应用Adapter模式,为每个控制器创建一个适配类。每个适配器类可以把标准接口调用转换为已有控制器支持的调用。

 

      图2引入了一个含有子类的MachineManager抽象类,其子类可转发对机器的控制调用命令,同时修改该类和其子类,以使它们适配FuserController和StartController.


           
                              图2  FuserManager和StartManager类通过向FuserController和

                      startController对象的对应方法传递调用来实现MachineManager的抽象方法

这样,如果机器控制器有一些操作,这些操作只针对某类特定的机器类型,那也是没有问题的。例如,FuserManager类也有一个switchSpool()方法,该方法会转发到FuserController对象的SwitchSpool()方法。

现在,我们就可以如下编写shutdown()方法,使其停止处理MachineManager类,取出正在流水线上的材料箱,停止机器的运转。

public void shutdown()
{
     stopProcess();
     converyOut();
     stopMachine();
}

 MachineManager类的shutdown()方法是具体的,而不是抽象的。然而,我们也可以说它是抽象的,因为关闭特定设备所需采取的步骤已被泛化。
2. 从抽象到Bridge模式: 

    基于不同机器的排列,来构造MachineManager的类层次结构,因此每种类型的机器都需要MachineManager类的不同子类。如果采取另外的排列方式,类层次结构将会有什么变化呢?假定我们就在机器本身上操作,这样它们就会提供所完成步骤的确认消息。相应地,我们需要一个MachineManager类的握手子类,它的方法可以让我们设置与机器交互的参数,例如设置一个超时值。然而,我们仍然需要不同的机器管理员来处理烟花火药球的压入和引线。如果我们没有首先重新组织MachineManager类层次结构,那么新的类层次结构可能如图3所示:


                 

                          图3  握手(Hsk)子类添加了获取某台实际机器的确认消息需要多长时间的参数

图3的类层次结构沿着两个方向来构造类:根据机器的类型和是否支持握手协议。构造过程中的这种双重规则产生了问题。特别是像setTimeout()这样的方法,可能在两个地方包含完全相同的代码,但是我们不能在这个类层次结构中构造它,因为超类不支持握手协议。 

      通常握手类没有共享代码的方法,因为根本就没有握手超类。我们在类层次结构中添加越多的类,问题就会变得更加糟糕。如果我们最终有5台机器的控制器,并决定改变setTimeout()方法,那么我们不得不在这5个地方更改代码。

      在这种情况下,我们可以使用Bridge模式。通过将MachineManager类的抽象方法移到独立的类层次结构,我们可以将MachineManager类与其抽象方法的实现分离。MachineManager类仍然是抽象类,但调用其方法的效果将依赖于我们是在控制焰火火药球的压入还是引线。

      将抽象方法的具体实现从抽象中分离出来,让两个类层次结构彼此独立。我们可以增加对新机器的支持,而不会影响MachineManager类层次结构。我们还可以扩展MachineManager类层次结构,而无需更改任何机器控制器。图4提出了我们期望的分离方案。

      新设计的目标是将MachineManager类层次结构与其中抽象方法的实现相分离。


               
                      图4   本图显示一种抽象,把机器管理者的类层次结构与抽象的driver对象的不同实现相分离
  注意,虽然图4中的MachineManager2类已经比较具体,虽然它仍然是抽象类。目前MachineManager类所依赖的抽象方法位于MachineDriver接口。这个接口的名字就意味着把MachineManage请求适配到特定机器的类已经成为驱动程序。驱动程序(driver)是一个对象,这个对象根据定义好的接口操作计算机系统或者外部设备。驱动程序是应用Bridge模式最常见的实例。

3. 应用Bridge模式的驱动程序

    驱动程序都是抽象的。应用程序的运行结果取决于它使用的是哪一个驱动程序。每个驱动程序都是Adapter模式的一个实例,它利用其他类的不同接口所提供的服务来实现用户要求的接口。使用驱动程序的总体设计就是Bridge模式的一个实例。这种设计模式将应用程序开发与驱动程序开发相分离,驱动程序实现应用程序所依赖的抽象方法

    基于驱动程序的设计强制我们对被驱动的机器或者系统创建一个公共的抽象模型。这样做的好处是,让抽象端的代码应用于任何可能被执行的驱动程序。为驱动程序定义一套通用的方法可能带来的问题是,清除了某个驱动程序实体可能支持的行为。现在回过头来看图1,引线控制器有一个switchSpool()方法。再看修正设计后的图4,该方法跑那儿去了呢?答案是我们将它抽象出去了。我们可以在新的FuserDriver类中包含switchSpool()方法。然而,这将导致抽象端的代码必须检查其驱动程序是否为一个FuserDriver实例。
    为避免丢失switchSpool()方法,我们可能会要求每个驱动程序都实现该方法。需要注意的是,某些驱动程序将会简单地忽略该调用。当选择一个驱动程序支持的方法的抽象模型时,我们将经常面临这种选择。可以包含某些驱动程序不支持的方法;或者剔除某些方法,这些方法要么将减少用驱动程序的抽象所能作的事情,要么强制抽象包含特殊情形下的代码。

4. 数据库驱动程序

    JDBC就是用于执行SQL语句的应用编程接口(API)。JDBC驱动程序就是实现该接口的类;数据库应用程序就是对数据库操作的抽象,它依赖于JDBC驱动程序;只要提供JDBC驱动程序,数据库应用程序就可以操作任何数据库。JDBC的这种架构将抽象与具体实现相分离,使得数据库应用程序和JDBC驱动程序能够独立地发展----- 这是Bridge模式的一个极好的例子。

    在使用JDBC驱动程序之前,需要先加载该驱动程序,连接到数据库,再创建Statement对象:

Class.forName(driverName);
Connection c= DriverManager.getConnection(url,user,pwd);
Statement stmt = c.createStatement();

代码中的变量stmt为Statement对象,它能够发出SQL查询,并获取结果集合:

ResultSet result = stmt.executeQuery(
     "SELECT name,apogee FROM firework");
while(result.next())
{
     String name = result.getString("name");
     int apogee = result.getInt("apogee");
     System.out.println(name+", "+apogee);
}

习题:假定在Oozinoz公司中,我们仅有SQL Server数据库。如果开发专用于SQL Server数据库的阅读器和适配器,存在什么问题?请同时说明我们为什么不应该这么做。

答:支持编写专用于SQL Server的代码的理由有两个:

     1. 我们不能预测未来,所以现在花费时间和金钱来准备也许根本不会发生的事件是非常错误的。我们现在拥有SQL Server,速度更快表示更短的反馈时间。

     2. 选中SQL Server数据库,我们可以使用这种数据库服务器提供的各种特性,而无需担心其他数据库驱动程序是否支持它。

      支持使用通用的SQL驱动程序的论点也有两个:

     1. 如果使用通用的SQL对象来编写代码,一旦我们更改数据库提供商,比如换成Oracle数据库,程序修改起来相当简单。如果把SQL Server数据库进行硬编码,我们就很难从竞争激烈的数据库市场受益。

     2. 使用通用的驱动程序使得我们可以编写试验代码,试验代码时完全可以使用廉价的数据库,诸如MySQL,这样就无需依赖测试SQL Server数据库。 

 

   JDBC架构清楚地划分了驱动程序开发者和应用程序开发者这两种角色。在有些情况下,即使我们正在使用驱动程序,这种划分也是事先不存在的。我们可以通过继承抽象超类来创建各个驱动程序子类,让每个子类驱动不同的子系统。在这种情况下,如果希望自己的应用程序和驱动程序更加灵活,就必须使用Bridge模式建模。

 

5. 小结

    抽象是指依赖于一系列抽象方法的类。最简单的抽象实例是抽象的类层次结构,其中超类中的具体类依赖于其他抽象类。如果希望用另一种机器的排列方式来构造最初的类层次结构,就必须把那些抽象的方法移到另一种类层次结构中。在这种情况下,我们可以使用Bridge模式,将抽象与抽象方法的实现相分离。

    Bridge模式最常见的例子就是驱动程序,比如数据库驱动程序。数据库驱动程序提供了Bridge模式结构中固有的权衡的良好实例。一个驱动程序可能会请求某个实现程序不支持的方法。另一方面,驱动程序可能会忽略应用到某个特定数据库的有用方法。这将迫使我们重新编写针对实现而不是抽象的代码。我们是否应该更重视抽象而不是具体并非一直都很明晰,但是有意地做这些决定是非常重要的。

  

你可能感兴趣的:(设计模式,数据结构,sql,sql,jdbc,server)