大卫的Design Patterns学习笔记06:Adapter

一、概述
Adapter(适配器)模式又称Wrapper模式,主要用于将一个类的接口转换成客户希望的另外一个接口,解决两个已有接口之间不匹配的问题。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。因此,Adapter模式经常被描述成第三方函数库或旧的程序库与现有系统接口 /需求不一致时的救星,也正是由于Adapter模式的这种特性,有人也将Adapter模式比喻成变压器,变压器把一种电压变换成另一种电压。美国的生活用电电压是110V,而中国的电压是220V。如果要在中国使用美国电器,就必须有一个能把220V电压转换成110V电压的变压器,这个变压器就是一个Adapter。

二、结构
在GoF的DP一书中给出了两种Adapter模式的应用:Class Adapter和Object Adapter,两种应用模式的类图如下所示:

1:Class Adapter
其中涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。

2:Object Adapter
其中涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:通过在内部包装(Wrap)一个Adaptee对象,把源接口转换成目标接口。

Class Adapter中adapter类通过继承adaptee类获得adaptee类的功能,而Object Adapter中adpter通过包容adaptee获得其功能。基于“当涉及到依存性时,应当始终优先选择组合 /成员关系而不是继承”的设计原则( <Exceptional C ++>),并且由于多继承在使用上的复杂性,及在部分情况下不可行等原因,Object Adapter的运用显得更加广泛。
此外,当需要改变多个已有子类的接口时,如果使用Class Adapter模式,就要针对每一个子类做一个适配器,而这不太实际,此时比较适合使用Object Adapter。
Object Adapter模式的基本思想在于:Delegate委托,即将需要完成的工作交给adaptee去完成,当然,为了兼容Client需求与adaptee接口之间的不一致,在委派的过程中,我们往往也需要作一些适当的调整。

三、应用
Adapter模式可应用于如下的情况:
1
、系统需要使用现有的类,而此类的接口不符合系统的需要。(示例 123均属于这种情况,只是出发点稍有不同)
2
、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有与一致的接口。这种情况从不同的角度考虑,可能被划入Facade模式的范畴,但从与现有设计适配的角度考虑该问题,则将其划归Adapter模式也是可以理解的。
3
、通过接口转换,将一个类插入另一个类系中。有人举过这样一个例子:虎与飞禽是没有直接关联的两类动物,但是现在出来了个“飞虎”,它同时具有虎肉食动物跟飞禽会飞的特质,要在飞禽这个类系中添加一个成员类“飞虎”,除了直接实现“飞虎”类,还有一种简单的办法是实现一个Adapter类,在其中包容一个虎的对象,同时实现飞禽的接口即可。当然,对于这个问题,多继承或者实现多接口可能是一个更直观的作法,在实际应用中,可视具体需要确定采用何种作法。

四、优缺点

五、举例
1
、出发点:委托实现,提供不同接口
在STL的实现中,我们可以轻易地找到很多Object Adapter的例子。如各种iterator最终的实现均是Delegate给具体的容器类实现的,以insert_iterator为例 :

template
 < class Container >
class
 insert_iterator  {
protected
:
  Container * container ;         // adaptee is here
  typename Container ::iterator iter ;
public
:
  typedef
 output_iterator_tag iterator_category ;
  typedef
 void                value_type ;
  typedef
 void                difference_type ;
  typedef
 void                pointer ;
  typedef
 void                reference ;

  insert_iterator (Container & x ,  typename Container ::iterator i )
    :
 container (&x ), iter (i ) {}
  insert_iterator <Container >&
  operator
=( const typename Container ::value_type & value ) {
    iter  = container ->insert (iter , value );     // delegate operation to adaptee
    ++iter ;
    return
 * this ;
  }

  insert_iterator <Container >&  operator *() {  return  * this ; }
  insert_iterator <Container >&  operator ++() {  return  * this ; }
  insert_iterator <Container >&  operator ++( int ) {  return  * this ; }
};


此外,std ::stack,std ::queue等也都是将相应的操作委托给deque完成的,而map实际上是通过包容rb_tree对象实现的。关于STL中应用Adapter的详细分析可以参考jjhou的 <STL源码剖析 >一书,jjhou分container adapter、iterator adpater、functor adpater三个方面深刻地剖析了STL中Adapter的运用。

2
、出发点:提供接口默认实现,简化客户代码
对于Java程序员而言,Adapter这个词可能再熟悉不过了,JDK中有很多Adapter类,如WindowAdapter、MouseAdapter等,分别实现了WindowListener、MouseListener等接口,这些都是Class Adapter的实例。由于这些接口非常常用,而我们往往只关心这些接口的某几个方法,但implements接口要求必须实现接口的所有方法,以至于我们经常不得已将接口方法实现为空。这实在是一件非常繁琐的事情,并且,代码中充斥着一些空方法,也会影响阅读,为此,JDK通过随接口一同发布的Adapter类为这些接口方法提供了缺省的空方法,我们只需要从Adapter类派生(实际上,由于Adapter类往往被用于编写匿名内部类,因此,连派生这一步都省了,编译器会自动为我们完成这一步),并为需要处理的接口方法提供实现即可。
这与上面讨论的Adapter的定义稍有不同,但本质上还是比较相似的:解决需求与实现间不一致的问题。

3
、出发点:接口转换
有时候,现有的实现在设计之初没有或者不便于实现一些基本功能以外的接口,当遇到这种问题的时候,一种解决方案是在现有的实现中添加这些接口的实现,但往往更好的办法是提供一个Adapter类,在维持原有接口的同时,达到支持新接口的目的。此外,在JDK中还存在着一些为了兼容老的设计而运用Adapter模式的例子,以下代码取自java .util .Collections:

public static
 Enumeration enumeration (final Collection c ) {
    return
 new Enumeration () {
        Iterator i  = c .iterator ();

        public
 boolean hasMoreElements () {
            return
 i .hasNext ();
        }


        public
 Object nextElement () {
            return
 i .next ();
        }
    };
}


其中的匿名类就是一个Adapter,目的是为了兼容老的Enumeration(Java1 .2修改了集合框架的设计,并引入了Iterator)。
对于普通应用而言,这种应用Adapter模式的情况比较常见。如一个用javax .swing .JTable显示AddressBook的应用,根据设计,我们不可能直接为AddressBook类实现JTable所需的TableModel接口(这会让其它使用AddressBook类的人莫名其妙),要在JTable中显示AddressBook,一种可行的作法是引入Adapter类,实现TableModel接口,在保持JTable和AddressBook独立性的基础上,通过引入Adapter类达到适配的目的。相应的示例代码如下所示:

import java .util .*;
import javax .swing .*;
import javax .swing .table .*;

class
 AddressItem  {
    public
 String name ;
    public
 String address ;
    public
 String tel ;
    // ...more attribute
    
    public
 AddressItem  (String name , String address , String tel ) {
        this
.name  = name ;
        this
.address  = address ;
        this
.tel  = tel ;
    }

    public
 String getName  () {
        return
 name ;
    }

    public
 String getAddress  () {
        return
 address ;
    }

    public
 String getTel  () {
        return
 tel ;
    }
}


class
 AddressBook  {
    List personList  =  new Vector ();
    
    public
 int getSize () {
        return
 personList .size ();
    }

    public
 boolean addItem  (AddressItem item ) {
        return
 personList .add (item );
    }

    public
 boolean addItem  (AddressItem [] aItem ) {
        return
 personList .addAll (Arrays .asList (aItem ));
    }

    public
 AddressItem getItem  ( int index ) {
        return
 (AddressItem )personList .get (index );
    }
}


class
 AddressBookTableAdapter extends AbstractTableModel
{

    AddressBook ab ;
    String [] columnName  = { "Name" ,  "Address" ,  "Tel." };
    
    public
 AddressBookTableAdapter  (AddressBook ab ) {
        this
.ab  = ab ;
    }

    
    // TableModel impl
    public  int getRowCount  () {
        return
 ab .getSize ();
    }

    public
 int getColumnCount  () {
        return
 columnName .length ;
    }

    public
 Object getValueAt  ( int row ,  int col ) {
        AddressItem item  = ab .getItem (row );
        if
 (item  == null )
            return
 null ;
            
        switch
 (col ) {
            case
 0 :
                return
 item .getName ();
            case
 1 :
                return
 item .getAddress ();
            case
 2 :
                return
 item .getTel ();
            default
:
                return
 null ;
        }
    }

    public
 String getColumnName  ( int c ) {
        return
 columnName [c ];
    }
}


 class
 AddressBookTest extends JFrame  {
    AddressItem [] aItem  = {
        new
 AddressItem ( "Bill" ,  "Sky Road. No.1" ,  "123" ),  
        new
 AddressItem ( "David" ,  "Sky Road. No.2" ,  "456" ),  
        new
 AddressItem ( "God" ,  "Sky Road. No.3" ,  "789" )
        };

    AddressBook ab  =  new AddressBook ();
    
    public
 AddressBookTest  () {
        ab .addItem (aItem );
        
        TableModel model  =  new AddressBookTableAdapter ( ab  );
        getContentPane ().add ( new JScrollPane ( new JTable (model )));
    }

    
    public static
 void  main  ( String [] args  ) {
        JFrame frame  =  new AddressBookTest ();
        frame .setTitle ( "Tables and Models" );
        frame .setBounds ( 300 ,  300 ,  450 ,  300 );
        frame .setDefaultCloseOperation (JFrame .DISPOSE_ON_CLOSE );
        frame .show ();
    }
}


4
、出发点:简化接口,分离实现
有些情况下,随着系统的升级和设计的修改,新的实现方式可能会对外提供新的接口,为了保持向下兼容,一种普遍的作法是在客户(使用我们接口的人,并非最终Customer)接口类中通过重载(或Factory Pattern等方式,参考笔记 2中Name Factory的例子)提供针对不同接口的实现。这种实现存在两个严重问题:
1
)代码难以维护,随着系统的升级,接口类将变得越来越臃肿;
2
)底层对客户过于透明,或者说,客户程序员要很好地使用接口类,必须对底层的实现策略有所了解。
一种改进的作法是:
针对不同接口提供不同的Adapter类,最终将接口统一起来。
这种实现方式不仅使得底层对客户程序员透明,也使得今后进行系统扩展所受到的束缚和需要进行的修改更小。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
现代C++中的设计模式是用于对象重用的可重复性方法。设计模式是一种在不同情况下解决相似问题的经验总结,可以通过将问题解决方案的关键部分抽象出来,从而提供灵活性和可重用性。设计模式不是编程语言特定的功能,而是一种通用的方法论。 在现代C++中,有许多常用的设计模式可以用于对象的可重用性。以下是几个常见的设计模式示例: 1.单例模式:用于确保一个类只能创建一个实例,并提供对该实例的全局访问点。对于有些对象只需要一个实例的情况,单例模式可以确保该实例的唯一性,从而方便访问和管理。 2.工厂模式:用于创建对象的过程中封装创建逻辑,让客户端代码无需关心对象的具体创建细节。通过工厂模式,可以通过一个工厂类来创建对象,从而提供更高的灵活性和可扩展性。 3.观察者模式:用于对象之间的发布-订阅机制,让一个对象(主题)的状态发生变化时,能够通知并自动更新其他依赖于该对象的对象(观察者)。通过观察者模式,可以实现对象之间的松耦合和消息传递,提高对象的可重用性和可维护性。 4.适配器模式:用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以解决接口不兼容的问题,从而使得原本不兼容的类能够一起工作,提高可重用性和互操作性。 5.策略模式:用于定义一系列算法/行为,并将其封装成独立的类,使得它们可以互相替换。策略模式可以在运行时根据需要动态切换算法/行为,从而提供更高的灵活性和可重用性。 这些设计模式都是在现代C++中常见且有用的重用性方法,可以根据具体的应用场景选择合适的设计模式来提高代码的可维护性、可扩展性和可重用性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值