适配器模式(Adapter Pattern)

来源:互联网 发布:nginx windows版优化 编辑:程序博客网 时间:2024/06/13 13:29
        结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。 
       结构型模式可以分为类结构型模式对象结构型模式
       (1)类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
       (2) 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

适配器模式结构

       适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
       在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如下图所示:

       在对象适配器模式结构图中包含如下几个角色:

        Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

        Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

        Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。


适配器模式实现

解决方案一 对象适配器模式的实现

       根据对象适配器模式结构图,在对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:
public abstract class Target{    public abstract void request();}public class Adaptee{    public void specificRequest(){        // 适配者里的接口    }}public class Adapter extends Target {    private Adaptee adaptee; // 维持一个对适配者对象的引用    public Adapter(Adaptee adaptee) {        this.adaptee = adaptee;    }    public void request() {        adaptee.specificRequest(); // 转发调用    }}public class Client{    public static void main(String[] args) {        Adaptee adaptee = new Adaptee();        Target target = new Adapter(adaptee);        target.request();    }}
       为了让系统具备良好的灵活性和可扩展性,我们引入了工具类XMLUtil和配置文件,其中,XMLUtil类的代码如下所示:
import javax.xml.parsers.*;import org.w3c.dom.*;import org.xml.sax.SAXException;import java.io.*;public class XMLUtil {    // 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象    public static Object getBean() {        try {            // 创建文档对象            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();            DocumentBuilder builder = dFactory.newDocumentBuilder();            Document doc;            doc = builder.parse(new File("config.xml"));            // 获取包含类名的文本节点            NodeList nl = doc.getElementsByTagName("className");            Node classNode = nl.item(0).getFirstChild();            String cName = classNode.getNodeValue();            // 通过类名生成实例对象并将其返回            Class c = Class.forName(cName);            Object obj = c.newInstance();            return obj;        }        catch(Exception e) {            e.printStackTrace();            return null;        }    }}
       配置文件config.xml中存储了适配器类的类名,代码如下所示:
// 配置文件<?xml version="1.0"?><config>    <className>adaptee</className></config>
          客户端Client类的代码修改如下:
public class Client {    public static void main(String[] args) {        Target target;  // 针对抽象目标接口编程        target = (Target)XMLUtil.getBean(); // 读取配置文件,反射生成对象        target.request();    }}

解决方案二 类适配器模式的实现

       除了对象适配器模式之外,适配器模式还有一种形式,那就是类适配器模式,类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同,对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系,类适配器模式结构如下图所示:

        根据类适配器模式结构图,适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。

       典型的类适配器代码如下所示:

interface Target{    public abstract void request();}public class Adaptee{    public void specificRequest(){        // 适配者里的接口    }}public class Adapter extends Adaptee implements Target {    public void request() {        specificRequest();    }}public class Client{    public static void main(String[] args) {        Target target = new Adapter();        target.request();    }}
       由于Java、C#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adapter为最终(Final)类,也无法使用类适配器。在Java等面向对象编程语言中,大部分情况下我们使用的是对象适配器,类适配器较少使用。


模式扩展 缺省适配器

     缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。缺省适配器模式的定义如下:

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。

       缺省适配器模式结构如下图所示:


          在缺省适配器模式中,包含如下三个角色:

       ● ServiceInterface(适配者接口):它是一个接口,通常在该接口中声明了大量的方法。

       ● AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。

       ● ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法也不得不提供空实现。在有了缺省适配器之后,可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。


适应场景

       在以下情况下可以考虑使用适配器模式:

       (1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。

       (2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。


效果

       适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,在spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也使用了适配器模式。

1. 主要优点

       无论是对象适配器模式还是类适配器模式都具有如下优点:

       (1)  将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。

       (2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。

       (3) 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

      具体来说,类适配器模式还有如下优点:

      由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

      对象适配器模式还有如下优点:

      (1) 一个对象适配器可以把多个不同的适配者适配到同一个目标

      (2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

2. 主要缺点

     类适配器模式的缺点如下:

      (1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者

      (2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;

      (3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

      对象适配器模式的缺点如下:

      与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。


应用实例

设计问题:COS系统是一款订餐软件,在COS系统中,MViewer类显示菜单列表MList时,使用数据结构类型是List<HashMap<String,String>>,而数据源对象类Mdata只提供游标Cursor数据结构类型。那么应该如何设计解决这个问题?

解决方案:适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。现用适配器模式解决这个问题,在COS系统中,MList是目标数据接口,Mdata是适配者类,MAdapter是适配器类,其类结构图如下所示:


       关键代码实现如下所示;

public abstract class MList {    public abstract List<HashMap<String, String>> getData();}public class Mdata{    public Cursor retrieveData(){        // 返回Cursor数据类型    }}public class MAdapter extends MList {    Mdata mdata;    public MAdapter(Mdata mdata) {        this.mdata = mdata;    }    public List<HashMap<String, String>> getData() {        Cursor cursor = mdata.retrieveData();        // 构建List<HashMap<String, String>>,实现数据转换        return;    }}public class MViewer{    Mdata mdata = new Mdata();    MList mlist = new MAdapter(mdata);    mlist.getData();}


参考博客:http://blog.csdn.net/lovelion/article/details/8624412

参考书籍:http://www.chinasa.info/

code download:https://github.com/JyNeo/Software-Design-Pattern/blob/master/DesignPattern_CoreCode/AdapterPattern.java

1 0
原创粉丝点击