java 设计模式-结构模式之桥梁模式

1. 桥梁模式简介

      桥梁模式(Bridge)是一个非常有用的模式,也是比较复杂的一个模式。熟悉这个模式对于理解面向对象的设计原则,包括"开-闭"原则(OCP)以及组合/聚合复用原则(CARP)都很有帮助。理解好这两个原则,有助于形成正确的设计思想和培养良好的设计风格。

      注:《Java与模式》一书认为Bridge模式不是一个使用频率很高的模式,我不太赞同,我认为Bridge模式中蕴涵了很多设计模式的关键思想在里面,所以我这里采纳了《Design Patterns Explained》一书的作者Alan Shalloway与James R. Trott的观点:The Bridge pattern is quite a bit more complex than the other patterns you just learned; it is also much more useful.

1.1. 桥梁模式的用意 

     【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。

1.2. 抽象化

       存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。

1.3. 实现化

       抽象化给出的具体实现,就是实现化。

1.4. 脱耦

       所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。

       将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥梁模式的用意。

2. 桥梁模式的结构

       桥梁模式【GOF95】是对象的结构模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

       下图所示就是一个实现了桥梁模式的示意性系统的结构图。

      下面的图更加清晰

      可以看出,这个系统含有两个等级结构,也就是:

  •           由抽象化角色和修正抽象化角色组成的抽象化等级结构。
  •           由实现化角色和两个具体实现化角色所组成的实现化等级结构。 

      桥梁模式所涉及的角色有:

  •           抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
  •           修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  •           实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以    非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
  •           具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。

      可是这样的结构能够给我们带来什么呢??
      系统设计中,总是充满了各种变数,这是防不慎防的。比如客户代表可能要求修改某个需求,增加某种功能等等。面对这样那样的变动,你只能去不停的修改设计和代码,并且要开始新的一轮测试……。
      那采取什么样的方式可以较好的解决变化带给系统的影响?你可以分析变化的种类,将不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类来分别实现。这样面向客户的只是一个抽象类,这种方式可以较好的避免为抽象类中现有接口添加新的实现所带来的影响,缩小了变化带来的影响。但是这可能会造成子类数量的爆炸,并且在某些时候不是很灵活。
      但是当你各个子类的行为经常发生变化,或者有一定的重复和组合关系时,我们不妨将这些行为提取出来,也采用接口的方式提供出来,然后以组合的方式将服务提供给原来的子类。这样就达到了前端和被使用的后端独立的变化,而且还达到了后端的重用。
      其实这就是桥梁模式的诞生。

3. 桥梁模式的示意性源代码

[java]  view plain copy
  1. //  Bridge pattern -- Structural example     
  2.  using  System;  
  3.   
  4.  //  "Abstraction"   
  5.  class  Abstraction  
  6.   {  
  7.    //  Fields   
  8.     protected  Implementor implementor;  
  9.   
  10.    //  Properties   
  11.     public  Implementor Implementor  
  12.     {  
  13.      set  { implementor  =  value; }   
  14.   }   
  15.    
  16.    //  Methods   
  17.     virtual   public   void  Operation()  
  18.     {  
  19.     implementor.Operation();  
  20.   }   
  21. }   
  22.    
  23.  //  "Implementor"   
  24.  abstract   class  Implementor  
  25.   {  
  26.    //  Methods   
  27.     abstract   public   void  Operation();  
  28. }   
  29.    
  30.  //  "RefinedAbstraction"   
  31.  class  RefinedAbstraction : Abstraction  
  32.   {  
  33.    //  Methods   
  34.     override   public   void  Operation()  
  35.     {  
  36.     implementor.Operation();  
  37.   }   
  38. }   
  39.    
  40.  //  "ConcreteImplementorA"   
  41.  class  ConcreteImplementorA : Implementor  
  42.   {  
  43.    //  Methods   
  44.     override   public   void  Operation()  
  45.     {  
  46.     Console.WriteLine( " ConcreteImplementorA Operation " );  
  47.   }   
  48. }   
  49.    
  50.  //  "ConcreteImplementorB"   
  51.  class  ConcreteImplementorB : Implementor  
  52.   {  
  53.    //  Methods   
  54.     override   public   void  Operation()  
  55.     {  
  56.     Console.WriteLine( " ConcreteImplementorB Operation " );  
  57.   }   
  58. }   
  59.    
  60.   /**/ ///   <summary>   
  61.  ///  Client test  
  62.  ///   </summary>   
  63.  public   class  Client  
  64.   {  
  65.    public   static   void  Main(  string [] args )  
  66.     {  
  67.     Abstraction abstraction  =   new  RefinedAbstraction();  
  68.   
  69.      //  Set implementation and call   
  70.      abstraction.Implementor  =   new  ConcreteImplementorA();  
  71.     abstraction.Operation();  
  72.   
  73.      //  Change implemention and call   
  74.      abstraction.Implementor  =   new  ConcreteImplementorB();  
  75.     abstraction.Operation();  
  76.   }   
  77. }  

4. 调制解调器问题

      感觉《敏捷软件开发-原则、模式与实践》中关于Bridge模式的例子很好。(《Java与模式》一书33章的对变化的封装一节也写得很不错,推荐大家读一读。它深入的阐述了《Design Patterns Explained》一书中"1)Design to interfaces. 2)Favor composition over inheritance. 3)Find what varies and encapsulate it"的三个观点。)。

      如图所示,有大量的调制解调器客户程序在使用Modem接口。Modem接口被几个派生类HayesModem、USRoboticsModem和EarniesModem实现。它很好地遵循了OCP、LSP和DIP。当增加新种类的调制解调器时,调制解调器的客户程序不会受影响。

      假定这种情形持续了几年,并有许多调制解调器的客户程序都在使用着Modem接口。现出现了一种不拨号的调制解调器,被称为专用调制解调器。它们位于一条专用连接的两端。有几个新应用程序使用这些专用调制解调器,它们无需拨号。我们称这些使用者为DedUser。但是,客户希望当前所有的调制解调器客户程序都可以使用这些专用调制解调器。他们不希望去更改许许多多的调制解调器客户应用程序,所以完全可以让这些调制解调器客户程序去拨一些假(dummy)电话号码。

      如果能选择的话,我们会把系统的设计更改为下图所示的那样。

      我们把拨号和通信功能分离为两个不同的接口。原来的调制解调器实现这两个接口,而调制解调器客户程序使用这两个接口。DedUser只使用Modem接口,而DedicateModem只实现Modem接口。但这样做会要求我们更改所有的调制解调器客户程序--这是客户不允许的。

      一个可能的解决方案是让DedicatedModem从Modem派生并且把dial方法和hangup方法实现为空,就像下面这样:

      几个月后,已经有了大量的DedUser,此时客户提出了一个新的更改。为了能拨国际电话号码、信用卡电话、PIN标识电话等等,必修对现有dial中使用char[10]存储号码改为能够拨打任意长度的电话号码。
      显然,所有的调制解调器客户程序都必须更改。客户同意了对调制解调器客户程序的更改,因为他们别无选择。糟糕的是,现在必须要去告诉DedUser的编写者,他们必须要更改他们的代码!你可以想象他们听到这个会有多高兴。本来他们是不用调用dial的。
      这就是许多项目都会具有的那种有害的混乱依赖关系。系统某一部分中的一个杂凑体(kludge)创建了一个有害的依赖关系,最终导致系统中完全无关的部分出现问题。
      如果使用ADAPTER模式解决最初的问题的话,就可以避免这个严重问题。如图:

BRIDGE模式

     看待这个问题,还有另外一个方式。现在,出现了另外一种切分Modem层次结构的方式。如下图:

      这不是一个理想的结构。每当增加一款新硬件时,就必须创建两个新类--一个针对专用的情况,一个针对拨号的情况。每当增加一种新连接类型时,就必须创建3个新类,分别对应3款不同的硬件。如果这两个自由度根本就是不稳定的,那么不用多久,就会出现大量的派生类。

      在类型层次结构具有多个自由度的情况中,BRIDGE模式通常是有用的。我们可以把这些层次结构分开并通过桥把它们结合到一起,而不是把它们合并起来。如图:

      我们把调制解调器类层次结构分成两个层次结构。一个表示连接方法,另一个表示硬件。

      这个结构虽然复杂,但是很有趣。它的创建不会影响到调制解调器的使用者,并且还完全分离了连接策略和硬件实现。ModemConnectController的每个派生类代表了一个新的连接策略。在这个策略的实现中可以使用sendlmp、receivelmp、diallmp和hanglmp。新imp方法的增加不会影响到使用者。可以使用ISP来给连接控制类增加新的接口。这种做法可以创建出一条迁移路径,调制解调器的客户程序可以沿着这条路径慢慢地得到一个比dial和hangup层次更高的API。

5. 另外一个实际应用Bridge模式的例子

      该例子演示了业务对象(BusinessObject)通过Bridge模式与数据对象(DataObject)解耦。数据对象的实现可以在不改变客户端代码的情况下动态进行更换。

[java]  view plain copy
  1. //  Bridge pattern -- Real World example   
  2.  using  System;  
  3.  using  System.Collections;  
  4.   
  5.  //  "Abstraction"   
  6.  class  BusinessObject  
  7.   {  
  8.    //  Fields   
  9.     private  DataObject dataObject;  
  10.    protected   string  group;  
  11.   
  12.    //  Constructors   
  13.     public  BusinessObject(  string  group )  
  14.     {  
  15.      this .group  =  group;  
  16.   }   
  17.    
  18.    //  Properties   
  19.     public  DataObject DataObject  
  20.     {  
  21.      set  { dataObject  =  value; }   
  22.       get  {  return  dataObject; }   
  23.   }   
  24.    
  25.    //  Methods   
  26.     virtual   public   void  Next()  
  27.     { dataObject.NextRecord(); }   
  28.    
  29.    virtual   public   void  Prior()  
  30.     { dataObject.PriorRecord(); }   
  31.    
  32.    virtual   public   void  New(  string  name )  
  33.     { dataObject.NewRecord( name ); }   
  34.    
  35.    virtual   public   void  Delete(  string  name )  
  36.     { dataObject.DeleteRecord( name ); }   
  37.    
  38.    virtual   public   void  Show()  
  39.     { dataObject.ShowRecord(); }   
  40.    
  41.    virtual   public   void  ShowAll()  
  42.     {  
  43.     Console.WriteLine(  " Customer Group: {0} " , group );  
  44.     dataObject.ShowAllRecords();  
  45.   }   
  46. }   
  47.    
  48.  //  "RefinedAbstraction"   
  49.  class  CustomersBusinessObject : BusinessObject  
  50.   {  
  51.    //  Constructors   
  52.     public  CustomersBusinessObject(  string  group )  
  53.     :  base ( group )  {}   
  54.    
  55.    //  Methods   
  56.     override   public   void  ShowAll()  
  57.     {  
  58.      //  Add separator lines   
  59.      Console.WriteLine();  
  60.     Console.WriteLine(  " ------------------------ "  );  
  61.      base .ShowAll();  
  62.     Console.WriteLine(  " ------------------------ "  );  
  63.   }   
  64. }   
  65.    
  66.  //  "Implementor"   
  67.  abstract   class  DataObject  
  68.   {  
  69.    //  Methods   
  70.     abstract   public   void  NextRecord();  
  71.    abstract   public   void  PriorRecord();  
  72.    abstract   public   void  NewRecord(  string  name );  
  73.    abstract   public   void  DeleteRecord(  string  name );  
  74.    abstract   public   void  ShowRecord();  
  75.    abstract   public   void  ShowAllRecords();  
  76. }   
  77.    
  78.  //  "ConcreteImplementor"   
  79.  class  CustomersDataObject : DataObject  
  80.   {  
  81.    //  Fields   
  82.     private  ArrayList customers  =   new  ArrayList();  
  83.    private   int  current  =   0 ;  
  84.   
  85.    //  Constructors   
  86.     public  CustomersDataObject()  
  87.     {  
  88.      //  Loaded from a database   
  89.      customers.Add(  " Jim Jones "  );  
  90.     customers.Add(  " Samual Jackson "  );  
  91.     customers.Add(  " Allen Good "  );  
  92.     customers.Add(  " Ann Stills "  );  
  93.     customers.Add(  " Lisa Giolani "  );  
  94.   }   
  95.    
  96.    //  Methods   
  97.     public   override   void  NextRecord()  
  98.     {  
  99.      if ( current  <=  customers.Count  -   1  )  
  100.       current ++ ;  
  101.   }   
  102.    
  103.    public   override   void  PriorRecord()  
  104.     {  
  105.      if ( current  >   0  )  
  106.       current -- ;  
  107.   }   
  108.    
  109.    public   override   void  NewRecord(  string  name )  
  110.     {  
  111.     customers.Add( name );  
  112.   }   
  113.    
  114.    public   override   void  DeleteRecord(  string  name )  
  115.     {  
  116.     customers.Remove( name );  
  117.   }   
  118.    
  119.    public   override   void  ShowRecord()  
  120.     {  
  121.     Console.WriteLine( customers[ current ] );  
  122.   }   
  123.    
  124.    public   override   void  ShowAllRecords()  
  125.     {  
  126.      foreach (  string  name  in  customers )  
  127.       Console.WriteLine(  "   "   +  name );  
  128.   }   
  129. }   
  130.    
  131.   /**/ ///   <summary>   
  132.  ///  Client test  
  133.  ///   </summary>   
  134.  public   class  BusinessApp  
  135.   {  
  136.    public   static   void  Main(  string [] args )  
  137.     {  
  138.      //  Create RefinedAbstraction   
  139.      CustomersBusinessObject customers  =   
  140.        new  CustomersBusinessObject( "  Chicago  " );  
  141.   
  142.      //  Set ConcreteImplementor   
  143.      customers.DataObject  =   new  CustomersDataObject();  
  144.   
  145.      //  Exercise the bridge   
  146.      customers.Show();  
  147.     customers.Next();  
  148.     customers.Show();  
  149.     customers.Next();  
  150.     customers.Show();  
  151.     customers.New(  " Henry Velasquez "  );  
  152.   
  153.     customers.ShowAll();  
  154.   }   
  155. }   

6. 在什么情况下应当使用桥梁模式

     根据上面的分析,在以下的情况下应当使用桥梁模式:

  •          如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
  •          设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。 
  •          一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
  •          虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值