图9-1 电源适配器示意图,  将三个类的接口转换到客户愿意的此外二个接口澳门威尼斯人网址

【学习难度:★★☆☆☆,使用作用:★★★★☆】 

【学习难度:★★☆☆☆,使用频率:★★★★☆】
一向出处:http://woquanke.com/books/gof/
梳理和学习:https://github.com/BruceOuyang/boy-design-pattern
简书日期: 2018/03/09
简书首页:https://www.jianshu.com/p/0fb891a7c5ed

  1. 概述

本人的台式机电脑的工作电压是20V,而作者国的家园用电是220V,如何让20V的台式机电脑可以在220V的电压下办事?答案是引入一个电源适配器(AC
Adapter)
,俗称充电器或变压器,有了那些电源适配器,生活用电和台式机电脑即可包容,如图9-1所示:

不包容结构的协调——适配器形式(一)

本身的笔记本电脑的工作电压是20V,而小编国的家园用电是220V,怎么样让20V的台式机电脑可以在220V的电压下办事?答案是引入二个电源适配器(AC
Adapter),俗称充电器或变压器,有了那些电源适配器,生活用电和台式机电脑即可包容,如图9-1所示:

澳门威尼斯人网址 1

图9-1 电源适配器示意图

在软件开发中,有时也设有类似那种不合营的情况,大家也足以像引入三个电源适配器一样引入3个誉为适配器的剧中人物来协调这个存在不般配的构造,那种设计方案即为适配器方式。

9.1 没有源码的算法库

Sunny软件集团在很久在此之前曾支付了二个算法库,里面包含了有的常用的算法,例如排序算法和寻找算法,在拓展各项软件开发时寻常要求引用该算法库中的算法。在为某学校支付教务管理连串时,开发人士发现须要对学生战表进行排序和摸索,该连串的筹划人士早已付出了一个成绩操作接口ScoreOperation,在该接口中宣称了排序方法sort(int[])
和寻找方法search(int[],
int),为了增长排序和查找的频率,开发人士决定选拔算法库中的飞速排序算法类QuickSort和二分查找算法类BinarySearch,其中QuickSort的quickSort(int[])方法已毕了快捷排序,BinarySearch
的binarySearch (int[], int)方法已毕了二分查找。

由于一些原因,未来Sunny集团开发人士已经找不到该算法库的源代码,不可以直接通过复制和粘贴操作来重用其中的代码;部分开发人士已经针对ScoreOperation接口编程,若是再要求对该接口进行改动或须要大家一直采取QuickSort类和BinarySearch类将促成大气代码须求修改。

Sunny软件商店开发职员面对那一个从未源码的算法库,蒙受二个甜蜜而又烦恼的标题:怎样在既不改动现有接口又不需求其余算法库代码的底子上可以落到实处算法库的重用?

透过分析,大家简单得知,以往Sunny软件公司面对的难题不怎么类似本章最初阶所关联的电压难点,战绩操作接口ScoreOperation好比只资助20V电压的记录簿,而算法库好比220V的家庭用电,那两部分都没有主意再展开修改,而且它们原来是八个精光不相干的布局,如图9-2所示:

澳门威尼斯人网址 2

图9-2 需协调的多个系统的结构示意图

现行大家须要ScoreOperation接口可以和已有算法库一起工作,让它们在同一个序列中可以合作,最好的落成形式是充实2个看似电源适配器一样的适配器角色,通过适配器来协调那多少个原本不包容的布局。如何在软件开发中设计和促成适配器是本章大家即将化解的基本难题,上边就让大家规范启幕上学这种用于缓解不包容结构难题的适配器方式。

9.2 适配器方式概述

与电源适配器相似,在适配器格局中引入了三个被叫作适配器(Adapter)的卷入类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的兑现就是把客户类的伏乞转化为对适配者的照应接口的调用。也等于说:当客户类调用适配器的不二法门时,在适配器类的其少校调用适配者类的形式,而以此历程对客户类是透明的,客户类并不直接访问适配者类。因而,适配器让那3个由于接口不般配而不可以互相的类可以一并工作。

适配器格局可以将一个类的接口和另二个类的接口匹配起来,而无须修改原来的适配者接口和架空目的类接口。适配器方式定义如下:

适配器情势(Adapter
Pattern):将3个接口转换到客户愿意的另3个接口,使接口不般配的这些类可以共同工作,其别名为包装器(Wrapper)。适配器格局既可以作为类结构型格局,也足以看成对象结构型形式。

【注:在适配器方式定义中所提及的接口是指广义的接口,它可以代表3个措施仍然措施的聚集。】

在适配器格局中,大家通过扩充3个新的适配器类来缓解接口不合营的题材,使得本来从不其余关联的类可以协同工作。依据适配器类与适配者类的涉及差距,适配器格局可分为对象适配器和类适配器二种,在对象适配器情势中,适配器与适配者之间是事关关系;在类适配器格局中,适配器与适配者之间是一而再(或落到实处)关系。在实际上支付中,对象适配器的利用效用更高,对象适配器情势协会如图9-3所示:

澳门威尼斯人网址 3

图 9-3 对象适配器形式结构图

在目的适配器形式协会图中蕴藏如下多少个剧中人物:

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

  • Adapter(适配器类):适配器可以调用另八个接口,作为二个转换器,对Adaptee和Target进行适配,适配器类是适配器方式的焦点,在目标适配器中,它经过三番五次Target并涉嫌三个Adaptee对象使两岸爆发联系。

  • Adaptee(适配者类):适配者即被适配的角色,它定义了三个早已存在的接口,那几个接口需要适配,适配者类一般是3个具体类,包涵了客户愿意利用的事务方法,在一些意况下或然没有适配者类的源代码。

依据目的适配器情势结构图,在对象适配器中,客户端须要调用request()方法,而适配者类Adaptee没有该方法,然而它所提供的specificRequest()方法却是客户端所要求的。为了使客户端能够拔取适配者类,必要提供2个卷入类Adapter,即适配器类。这几个包裹类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是涉嫌关系(也可称之为委派关系),所以那种适配器格局称为对象适配器情势。典型的靶子适配器代码如下所示:

class Adapter extends Target {  
    private Adaptee adaptee; //维持一个对适配者对象的引用  

    public Adapter(Adaptee adaptee) {  
        this.adaptee=adaptee;  
    }  

    public void request() {  
        adaptee.specificRequest(); //转发调用  
    }  
}

思考

在目的适配器中,2个适配器能不能适配七个适配者?如若能,应该怎么贯彻?假使不可以,请表明原因?

  将3个类的接口转换来客户愿意的此外三个接口。Adapter方式使得原本由于接口不包容而不或者共同坐班的那多少个类可以在一块儿干活。

澳门威尼斯人网址 4

不般配结构的和谐——适配器格局(二)

9.3 完整消除方案

Sunny软件集团开发人士决定利用适配器方式来重用算法库中的算法,其中央结构如图9-4所示:

澳门威尼斯人网址 5

图9-4 算法库重用结构图

在图9-4中,ScoreOperation接口充当抽象目标,QuickSort和BinarySearch类充当适配者,OperationAdapter充当适配器。完整代码如下所示:

//抽象成绩操作类:目标接口  
interface ScoreOperation {  
    public int[] sort(int array[]); //成绩排序  
    public int search(int array[],int key); //成绩查找  
}  

//快速排序类:适配者  
class QuickSort {  
    public int[] quickSort(int array[]) {  
        sort(array,0,array.length-1);  
        return array;  
    }  

    public void sort(int array[],int p, int r) {  
        int q=0;  
        if(p<r) {  
            q=partition(array,p,r);  
            sort(array,p,q-1);  
            sort(array,q+1,r);  
        }  
    }  

    public int partition(int[] a, int p, int r) {  
        int x=a[r];  
        int j=p-1;  
        for (int i=p;i<=r-1;i++) {  
            if (a[i]<=x) {  
                j++;  
                swap(a,j,i);  
            }  
        }  
        swap(a,j+1,r);  
        return j+1;   
    }  

    public void swap(int[] a, int i, int j) {     
        int t = a[i];     
        a[i] = a[j];     
        a[j] = t;     
    }  
}  

//二分查找类:适配者  
class BinarySearch {  
    public int binarySearch(int array[],int key) {  
        int low = 0;  
        int high = array.length -1;  
        while(low <= high) {  
            int mid = (low + high) / 2;  
            int midVal = array[mid];  
            if(midVal < key) {    
                low = mid +1;    
            }  
            else if (midVal > key) {    
                high = mid -1;    
            }  
            else {    
                return 1; //找到元素返回1    
            }  
        }  
        return -1;  //未找到元素返回-1  
    }  
}  

//操作适配器:适配器  
class OperationAdapter implements ScoreOperation {  
    private QuickSort sortObj; //定义适配者QuickSort对象  
    private BinarySearch searchObj; //定义适配者BinarySearch对象  

    public OperationAdapter() {  
        sortObj = new QuickSort();  
        searchObj = new BinarySearch();  
    }  

    public int[] sort(int array[]) {    
        return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法  
    }  

    public int search(int array[],int key) {    
        return searchObj.binarySearch(array,key); //调用适配者类BinarySearch的查找方法  
    }
}

为了让系统具有优秀的布帆无恙和可增加性,大家引入了工具类XMLUtil和计划文件,其中,XMLUtil类的代码如下所示:

import javax.xml.parsers.*;  
import org.w3c.dom.*;  
import org.xml.sax.SAXException;  
import java.io.*;  
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>OperationAdapter</className>  
</config>

编写如下客户端测试代码:

class Client {  
    public static void main(String args[]) {  
        ScoreOperation operation;  //针对抽象目标接口编程  
        operation = (ScoreOperation)XMLUtil.getBean(); //读取配置文件,反射生成对象  
        int scores[] = {84,76,50,69,90,91,88,96}; //定义成绩数组  
        int result[];  
        int score;  

        System.out.println("成绩排序结果:");  
        result = operation.sort(scores);  

        //遍历输出成绩  
        for(int i : scores) {  
            System.out.print(i + ",");  
        }  
        System.out.println();  

        System.out.println("查找成绩90:");  
        score = operation.search(result,90);  
        if (score != -1) {  
            System.out.println("找到成绩90。");  
        }  
        else {  
            System.out.println("没有找到成绩90。");  
        }  

        System.out.println("查找成绩92:");  
        score = operation.search(result,92);  
        if (score != -1) {  
            System.out.println("找到成绩92。");  
        }  
        else {  
            System.out.println("没有找到成绩92。");  
        }  
    }  
}

编译并运营程序,输出结果如下:

成绩排序结果:
50,69,76,84,88,90,91,96,
查找成绩90:
找到成绩90。
查找成绩92:
没有找到成绩92。

在本实例中应用了目的适配器形式,同时引入了配置文件,将适配器类的类名存储在布局文件中。就算要求选择其余排序算法类和寻找算法类,可以追加2个新的适配器类,使用新的适配器来适配新的算法,原有代码无须修改。通过引入配置文件和反光机制,可以在不修改客户端代码的气象下利用新的适配器,无须修改源代码,符合“开闭原则”。

  1. 斩草除根的题材

图9-1 电源适配器示意图

不匹配结构的调和——适配器方式(三)

9.4 类适配器

除此之外对象适配器方式之外,适配器方式还有一种方式,那就是类适配器方式,类适配器方式和对象适配器格局最大的分别在于适配器和适配者之间的涉嫌不同,对象适配器格局中适配器和适配者之间是关乎关系,而类适配器格局中适配器和适配者是持续关系,类适配器情势协会如图9-5所示:

澳门威尼斯人网址 6

图 9-5 类适配器形式结构图

基于类适配器情势结构图,适配器类达成了用空想来欺骗别人目的类接口Target,并连续了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,落成了适配。

数一数二的类适配器代码如下所示:

class Adapter extends Adaptee implements Target {  
    public void request() {  
        specificRequest();  
    }  
}

由于Java、C#等语言不扶助多重类继承,因而类适配器的接纳受到广大限量,例如如若目的抽象类Target不是接口,而是1个类,就不只怕利用类适配器;其它,即使适配者Adapter为终极(Final)类,也无法选用类适配器。在Java等面向对象编程语言中,大多数状态下大家应用的是目的适配器,类适配器较少使用。

思考

在类适配器中,二个适配器能不能适配多个适配者?纵然能,应该什么促成?若是不只怕,请证实原因?

9.5 双向适配器

在目的适配器的采用进程中,假如在适配器中同时富含对目的类和适配者类的引用,适配者可以经过它调用目的类中的方法,目标类也可以由此它调用适配者类中的方法,那么该适配器就是1个双向适配器,其布局示意图如图9-6所示:
![图9-6 双向适配器结构示意图

澳门威尼斯人网址 7

two-way-adpater-structural-uml.jpg

双向适配器的贯彻相比较复杂,其独立代码如下所示:

class Adapter implements Target,Adaptee {  
    //同时维持对抽象目标类和适配者的引用  
    private Target target;  
    private Adaptee adaptee;  

    public Adapter(Target target) {  
        this.target = target;  
    }  

    public Adapter(Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  

    public void request() {  
        adaptee.specificRequest();  
    }  

    public void specificRequest() {  
        target.request();  
    }  
}

在事实上支付中,大家很少使用双向适配器。

  即Adapter情势使得原本由于接口不合营而无法共同坐班的那壹个类可以在协同干活。

     
在软件开发中,有时也存在类似那种不般配的景况,咱们也足以像引入三个电源适配器一样引入2个叫做适配器的角色来协调那些存在不包容的结构,那种设计方案即为适配器方式。

不般配结构的调和——适配器情势(四)

9.6 缺省适配器

缺省适配器情势是适配器格局的一种变体,其利用也相比广泛。缺省适配器情势的概念如下:

缺省适配器方式(Default Adapter
Pattern):当不必要贯彻三个接口所提供的享有办法时,可先设计1个抽象类完结该接口,并为接口中每种方法提供三个暗许完毕(空方法),那么该抽象类的子类可以选取性地掩盖父类的少数方法来完成必要,它适用于不想利用三个接口中的全体办法的景况,又称作单接口适配器方式。

缺省适配器情势结构如图9-7所示:

澳门威尼斯人网址 8

图9-7 缺省适配器方式结构图

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

  • ServiceInterface(适配者接口):它是多个接口,平常在该接口中宣称了汪洋的格局。

  • AbstractServiceClass(缺省适配器类):它是缺省适配器方式的中坚类,使用空方法的花样完成了在ServiceInterface接口中扬言的法门。经常将它定义为抽象类,因为对它举办实例化没有其余意义。

  • ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在尚未引入适配器此前,它必要完结适配者接口,由此须求贯彻在适配者接口中定义的有所办法,而对此部分并非使用的措施也只好提供空完毕。在有了缺省适配器之后,可以一直接轨该适配器类,依照需求有采取性地掩盖在适配器类中定义的法子。

在JDK类库的事件处理包java.awt.event中普遍采纳了缺省适配器格局,如WindowAdapter、Key艾达pter、MouseAdapter等。上边大家以拍卖窗口事件为例来进展认证:在Java语言中,一般我们得以应用二种艺术来完结窗口事件处理类,一种是通过已毕WindowListener接口,另一种是透过两次三番Window艾达pter适配器类。假诺是行使第3种方法,直接已毕WindowListener接口,事件处理类须要贯彻在该接口中定义的三个章程,而对于超过半数急需或然只须要贯彻一多少个艺术,其余方式都毫不完结,但出于语言特色大家不得不为其余办法也提供1个总结的贯彻(寻常是空落成),那给采用带来了劳动。而使用缺省适配器形式就足以很好地化解这一题材,在JDK中提供了3个适配器类WindowAdapter来达成WindowListener接口,该适配器类为接口中的每3个情势都提供了1个空完结,此时事件处理类可以一而再WindowAdapter类,而无须再为接口中的每一种方法都提供完毕。如图9-8所示:

澳门威尼斯人网址 9

图9-8 WindowListener和WindowAdapter结构图

9.7 适配器形式总括

适配器形式将长存接口转化为客户类所期待的接口,达成了对现有类的复用,它是一种采纳成效十二分高的设计形式,在软件开发中得以广泛应用,在Spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也采用了适配器形式。

  1. 重在优点

无论对象适配器方式依旧类适配器格局都具备如下优点:

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

(2)
扩张了类的透明性和复用性,将现实的业务完结进度封装在适配者类中,对于客户端类而言是晶莹剔透的,而且提升了适配者的复用性,同三个适配者类可以在多少个不一致的种类中复用。

(3)
灵活性和增加性都十分好,通过动用布署文件,可以很有益地转移适配器,也得以在不修改原有代码的底蕴上平添新的适配器类,完全符合“开闭原则”。

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

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

目标适配器形式还有如下优点:

(1) 贰个目的适配器可以把七个例外的适配者适配到同三个对象;

(2)
可以适配1个适配者的子类,由于适配器和适配者之间是涉嫌关系,依照“里氏代换原则”,适配者的子类也可透过该适配器举行适配。

  1. 主要弱点

类适配器方式的症结如下:

(1)
对于Java、C#等不援救多重类继承的言语,四遍最八只好适配二个适配者类,不或但是且适配七个适配者;

(2) 适配者类不恐怕为最终类,如在Java中不可以为final类,C#中无法为sealed类;

(3)
在Java、C#等语言中,类适配器情势中的目的抽象类只可以为接口,不能为类,其使用有自然的局限性。

对象适配器形式的败笔如下:

与类适配器格局相比较,要在适配器中置换适配者类的少数方法相比麻烦。若是一定要置换掉适配者类的八个或多个法子,可以先做多个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真的的适配者进行适配,落成进度比较复杂。

  1. 适用场景

在以下景况下得以考虑采纳适配器格局:

(1)
系统须要动用一些存世的类,而那几个类的接口(如方法名)不符合系统的急需,甚至未曾那几个类的源代码。

(2)
想创建三个得以重复使用的类,用于与部分相互之间没有太大关系的一些类,包含一些大概在明日引进的类一起干活。

练习

Sunny软件集团OA系统须要提供2个加密模块,将用户机密音信(如口令、邮箱等)加密事后再囤积在数据库中,系统现已定义好了数据库操作类。为了升高支付功能,现须求选定已有的加密算法,那个算法封装在局地由第二方提供的类中,有个别甚至不曾源代码。试使用适配器形式设计该加密模块,实将来不修改现有类的功底上重用第贰方加密方法。

勤学苦练会在我的github上做掉

      下边是三个尤其形象的例证

 

       澳门威尼斯人网址 10

9.1 没有源码的算法库

       Sunny软件公司在很久以前曾开发了一个算法库,里面包含了一些常用的算法,例如排序算法和查找算法,在进行各类软件开发时经常需要重用该算法库中的算法。在为某学校开发教务管理系统时,开发人员发现需要对学生成绩进行排序和查找,该系统的设计人员已经开发了一个成绩操作接口ScoreOperation,在该接口中声明了排序方法sort(int[]) 和查找方法search(int[], int),为了提高排序和查找的效率,开发人员决定重用算法库中的快速排序算法类QuickSort和二分查找算法类BinarySearch,其中QuickSort的quickSort(int[])方法实现了快速排序,BinarySearch 的binarySearch (int[], int)方法实现了二分查找。

       由于某些原因,现在Sunny公司开发人员已经找不到该算法库的源代码,无法直接通过复制和粘贴操作来重用其中的代码;部分开发人员已经针对ScoreOperation接口编程,如果再要求对该接口进行修改或要求大家直接使用QuickSort类和BinarySearch类将导致大量代码需要修改。

       Sunny软件公司开发人员面对这个没有源码的算法库,遇到一个幸福而又烦恼的问题:如何在既不修改现有接口又不需要任何算法库代码的基础上能够实现算法库的重用?

       通过分析,大家简单得知,以往Sunny软件公司面对的标题不怎么类似本章最发轫所关联的电压难点,成绩操作接口ScoreOperation好比只协理20V电压的记录本,而算法库好比220V的家中用电,那两片段都没有主意再开展改动,而且它们原来是八个完全不相干的布局,如图9-2所示:

澳门威尼斯人网址 11

图9-2 需协调的三个连串的布局示意图

      
今后大家须求ScoreOperation接口可以和已有算法库一起坐班,让它们在同一个连串中可见包容,最好的完结格局是增多贰个看似电源适配器一样的适配器剧中人物,通过适配器来协调那多少个原本不同盟的布局。怎样在软件开发中统筹和促成适配器是本章大家就要化解的着力难点,下边就让大家专业开班上学那种用于化解不同盟结构难点的适配器格局。

 

9.2 适配器情势概述

      
与电源适配器相似,在适配器方式中引入了二个被称作适配器(Adapter)的包装类,而它所包装的靶子称为适配者(Adaptee),即被适配的类。适配器的兑现就是把客户类的央浼转化为对适配者的对应接口的调用。也等于说:当客户类调用适配器的艺术时,在适配器类的其上将调用适配者类的不二法门,而以此历程对客户类是晶莹剔透的,客户类并不间接访问适配者类。由此,适配器让那多少个由于接口不般配而不只怕相互的类可以联手干活。

      
适配器格局可以将2个类的接口和另3个类的接口匹配起来,而无须修改原来的适配者接口和浮泛目标类接口。适配器格局定义如下:

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

【注:在适配器格局定义中所提及的接口是指广义的接口,它可以象征贰个办法仍旧措施的集结。】

      
在适配器形式中,我们经过扩张一个新的适配器类来缓解接口不兼容的题材,使得原本没有其他关联的类可以协同工作。根据适配器类与适配者类的关联差异,适配器格局可分为对象适配器和类适配器三种,在对象适配器方式中,适配器与适配者之间是涉及关系;在类适配器格局中,适配器与适配者之间是两次三番(或落到实处)关系。在骨子里开支中,对象适配器的运用效用更高,对象适配器形式结构如图9-3所示:

澳门威尼斯人网址 12

图 9-3 对象适配器情势结构图

       在目的适配器形式结构图中包含如下多少个角色:

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

      
 Adapter(适配器类):适配器可以调用另三个接口,作为2个转换器,对Adaptee和Target举办适配,适配器类是适配器方式的中央,在目标适配器中,它通过一连Target并涉及3个Adaptee对象使双边产生联系。

      
● Adaptee(适配者类):适配者即被适配的角色,它定义了三个已经存在的接口,那个接口须要适配,适配者类一般是壹个具体类,包罗了客户愿意选取的事体方法,在有个别景况下恐怕没有适配者类的源代码。

      
依据目标适配器情势结构图,在目的适配器中,客户端需求调用request()方法,而适配者类Adaptee没有该方式,但是它所提供的specificRequest()方法却是客户端所须求的。为了使客户端可以利用适配者类,需求提供一个包裹类Adapter,即适配器类。那一个包裹类包装了2个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是涉嫌关系(也可称之为委派关系),所以那种适配器方式称为对象适配器格局。典型的目的适配器代码如下所示:

[java] view
plain
 copy

 

  1. class Adapter extends Target {  
  2.     private Adaptee adaptee; //维持二个对适配者对象的引用  
  3.       
  4.     public Adapter(Adaptee adaptee) {  
  5.         this.adaptee=adaptee;  
  6.     }  
  7.       
  8.     public void request() {  
  9.         adaptee.specificRequest(); //转载调用  
  10.     }  
  11. }  

 

 

思考

       在对象适配器中,一个适配器能否适配多个适配者?如果能,应该如何实现?如果不能,请说明原因?

      澳门威尼斯人网址 13

9.3 完整消除方案

     
萨妮软件商店开发人士决定利用适配器情势来重用算法库中的算法,其主题结构如图9-4所示:

澳门威尼斯人网址 14

图9-4  算法库重用结构图

      
在图9-4中,ScoreOperation接口充当抽象目的,QuickSort和BinarySearch类充当适配者,OperationAdapter充当适配器。完整代码如下所示:

//抽象成绩操作类:目标接口  
interface ScoreOperation {  
    public int[] sort(int array[]); //成绩排序  
    public int search(int array[],int key); //成绩查找  
}  

//快速排序类:适配者  
class QuickSort {  
    public int[] quickSort(int array[]) {  
        sort(array,0,array.length-1);  
        return array;  
    }  

    public void sort(int array[],int p, int r) {  
        int q=0;  
        if(p<r) {  
            q=partition(array,p,r);  
            sort(array,p,q-1);  
            sort(array,q+1,r);  
        }  
    }  

    public int partition(int[] a, int p, int r) {  
        int x=a[r];  
        int j=p-1;  
        for (int i=p;i<=r-1;i++) {  
            if (a[i]<=x) {  
                j++;  
                swap(a,j,i);  
            }  
        }  
        swap(a,j+1,r);  
        return j+1;   
    }  

    public void swap(int[] a, int i, int j) {     
        int t = a[i];     
        a[i] = a[j];     
        a[j] = t;     
    }  
}  

//二分查找类:适配者  
class BinarySearch {  
    public int binarySearch(int array[],int key) {  
        int low = 0;  
        int high = array.length -1;  
        while(low <= high) {  
            int mid = (low + high) / 2;  
            int midVal = array[mid];  
            if(midVal < key) {    
low = mid +1;    
}  
            else if (midVal > key) {    
high = mid -1;    
}  
            else {    
return 1; //找到元素返回1    
}  
        }  
        return -1;  //未找到元素返回-1  
    }  
}  

//操作适配器:适配器  
class OperationAdapter implements ScoreOperation {  
    private QuickSort sortObj; //定义适配者QuickSort对象  
    private BinarySearch searchObj; //定义适配者BinarySearch对象  

    public OperationAdapter() {  
        sortObj = new QuickSort();  
        searchObj = new BinarySearch();  
    }  

    public int[] sort(int array[]) {    
return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法  
}  

    public int search(int array[],int key) {    
return searchObj.binarySearch(array,key); //调用适配者类BinarySearch的查找方法  
}  
}  

  为了让系统有着杰出的油滑和可扩充性,大家引入了工具类XMLUtil和配备文件,其中,XMLUtil类的代码如下所示:

import javax.xml.parsers.*;  
import org.w3c.dom.*;  
import org.xml.sax.SAXException;  
import java.io.*;  
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中储存了适配器类的类名,代码如下所示:

[html] view
plain
 copy

 

  1. <?xml version=”1.0″?>  
  2. <config>  
  3.     <className>OperationAdapter</className>  
  4. </config>  

澳门威尼斯人网址 15

       编写如下客户端测试代码:

class Client {  
    public static void main(String args[]) {  
        ScoreOperation operation;  //针对抽象目标接口编程  
        operation = (ScoreOperation)XMLUtil.getBean(); //读取配置文件,反射生成对象  
        int scores[] = {84,76,50,69,90,91,88,96}; //定义成绩数组  
        int result[];  
        int score;  

        System.out.println("成绩排序结果:");  
        result = operation.sort(scores);  

        //遍历输出成绩  
        for(int i : scores) {  
            System.out.print(i + ",");  
        }  
        System.out.println();  

        System.out.println("查找成绩90:");  
        score = operation.search(result,90);  
        if (score != -1) {  
            System.out.println("找到成绩90。");  
        }  
        else {  
            System.out.println("没有找到成绩90。");  
        }  

        System.out.println("查找成绩92:");  
        score = operation.search(result,92);  
        if (score != -1) {  
            System.out.println("找到成绩92。");  
        }  
        else {  
            System.out.println("没有找到成绩92。");  
        }  
    }  
}  

编译并运维程序,输出结果如下:

成绩排序结果:

50,69,76,84,88,90,91,96,

查找成绩90:

找到成绩90。

查找成绩92:

没有找到成绩92。

      
在本实例中选拔了目的适配器情势,同时引入了布署文件,将适配器类的类名存储在配置文件中。如果急需动用此外排序算法类和搜索算法类,可以追加二个新的适配器类,使用新的适配器来适配新的算法,原有代码无须修改。通过引入配置文件和反光机制,可以在不修改客户端代码的情状下拔取新的适配器,无须修改源代码,符合“开闭原则”。

  1. 形式中的脚色

9.4 类适配器

       除了对象适配器形式之外,适配器情势还有一种样式,那就是类适配器格局,类适配器形式和对象适配器情势最大的不相同在于适配器和适配者之间的关联分裂,对象适配器形式中适配器和适配者之间是关联关系,而类适配器格局中适配器和适配者是再而三关系,类适配器模式协会如图9-5所示:

澳门威尼斯人网址 16

图 9-5 类适配器形式结构图

      
依据类适配器方式社团图,适配器类达成了抽象目的类接口Target,并继续了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,完成了适配。

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

[java] view
plain
 copy

 

  1. class Adapter extends Adaptee implements Target {  
  2.     public void request() {  
  3.         specificRequest();  
  4.     }  
  5. }  

      
由于Java、C#等语言不帮助多重类继承,由此类适配器的运用受到许多限制,例如假若目标抽象类Target不是接口,而是二个类,就不只怕使用类适配器;其它,倘若适配者Adapter为最后(Final)类,也无力回天运用类适配器。在Java等面向对象编程语言中,一大半处境下大家使用的是目的适配器,类适配器较少使用。

 

思考

       在类适配器中,一个适配器能否适配多个适配者?如果能,应该如何实现?如果不能,请说明原因?

 

  

 

  3.1
目的接口(Target):客户所期望的接口。目的可以是具体的或抽象的类,也足以是接口。

9.5 双向适配器

       在对象适配器的利用进程中,如若在适配器中而且含有对目的类和适配者类的引用,适配者可以经过它调用目标类中的方法,目的类也可以透过它调用适配者类中的方法,那么该适配器就是2个双向适配器,其布局示意图如图9-6所示:

澳门威尼斯人网址 17

图9-6 双向适配器结构示意图

       双向适配器的贯彻相比复杂,其独立代码如下所示:

[java] view
plain
 copy

 

  1. class Adapter implements Target,Adaptee {  
  2.     //同时维持对抽象目标类和适配者的引用  
  3.     private Target target;  
  4.     private Adaptee adaptee;  
  5.       
  6.     public Adapter(Target target) {  
  7.         this.target = target;  
  8.     }  
  9.       
  10.     public Adapter(Adaptee adaptee) {  
  11.         this.adaptee = adaptee;  
  12.     }  
  13.       
  14.     public void request() {  
  15.         adaptee.specificRequest();  
  16.     }  
  17.       
  18.     public void specificRequest() {  
  19.         target.request();  
  20.     }  
  21. }  

       在实际上开发中,我们很少使用双向适配器。

  3.2 须求适配的类(Adaptee):须要适配的类或适配者类。

9.7 适配器情势总计

     
适配器情势将长存接口转化为客户类所梦想的接口,完成了对现有类的复用,它是一种采纳频率十二分高的设计形式,在软件开发中得以广泛应用,在spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也拔取了适配器格局。

 

       1. 根本优点

       无论是对象适配器方式依旧类适配器形式都享有如下优点:

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

      
(2) 增加了类的透明性和复用性,将切实的工作达成进程封装在适配者类中,对于客户端类而言是透明的,而且提升了适配者的复用性,同3个适配者类可以在三个例外的连串中复用。

      
(3) 世故和伸张性都充裕好,通过应用安插文件,可以很有利地转换适配器,也得以在不改动原有代码的基本功上平添新的适配器类,完全符合“开闭原则”。

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

     
由于适配器类是适配者类的子类,因而可以在适配器类中置换一些适配者的点子,使得适配器的八面见光更强。

      对象适配器情势还有如下优点:

      (1) 多少个指标适配器可以把多少个区其他适配者适配到同3个对象

     
(2) 可以适配二个适配者的子类,由于适配器和适配者之间是关乎关系,依照“里氏代换原则”,适配者的子类也可透过该适配器举办适配。

 

      2. 根本症结

     类适配器方式的瑕疵如下:

     
(1) 对于Java、C#等不扶助多重类继承的语言,一回最多只好适配一个适配者类,不可以同时适配五个适配者

     
(2) 适配者类不可以为最后类,如在Java中无法为final类,C#中不可能为sealed类;

     
(3) 在Java、C#等语言中,类适配器情势中的目的抽象类只可以为接口,不可以为类,其行使有自然的局限性。

      对象适配器情势的老毛病如下:

     
与类适配器方式比较,要在适配器中置换适配者类的一些方法相比较麻烦。若是一定要置换掉适配者类的二个或两个办法,可以先做三个适配者类的子类,将适配者类的点子置换掉,然后再把适配者类的子类当做真的的适配者举办适配,完成进度相比复杂。

 

      3. 适用场景

      在偏下情况下得以设想使用适配器形式:

      
(1) 系统要求使用部分共处的类,而这几个类的接口(如方法名)不适合系统的内需,甚至尚未这一个类的源代码。

      
(2) 想创造三个方可重复使用的类,用于与局地互相之间没有太大关系的部分类,包蕴一些只怕在以后推荐的类一起坐班。

 

练习

       Sunny软件公司OA系统需要提供一个加密模块,将用户机密信息(如口令、邮箱等)加密之后再存储在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。试使用适配器模式设计该加密模块,实现在不修改现有类的基础上重用第三方加密方法。

 

 

【作者:刘伟  http://blog.csdn.net/lovelion

  3.3
适配器(艾达pter):通过包装1个亟需适配的靶子,把原接口转换到目的接口。  

  1. 落到实处情势

   (1)类的适配器形式(选用继承完成)

     (2)对象适配器(拔取对象组合措施已毕)

适配器情势的类图

澳门威尼斯人网址 18

 

一。类的适配器形式

[java] view
plain

copy
print?澳门威尼斯人网址 19澳门威尼斯人网址 20

  1. // 已存在的、具有特出功能、但不相符大家既有的标准接口的类  
  2. class Adaptee {  
  3.     public void specificRequest() {  
  4.         System.out.println(“被适配类具有 特殊意义…”);  
  5.     }  
  6. }  
  7.   
  8. // 目标接口,或称为标准接口  
  9. interface Target {  
  10.     public void request();  
  11. }  
  12.   
  13. // 具体目的类,只提供常备意义  
  14. class ConcreteTarget implements Target {  
  15.     public void request() {  
  16.         System.out.println(“普通类 具有 普通意义…”);  
  17.     }  
  18. }  
  19.    
  20. // 适配器类,继承了被适配类,同时落到实处标准接口  
  21. class Adapter extends Adaptee implements Target{  
  22.     public void request() {  
  23.         super.specificRequest();  
  24.     }  
  25. }  
  26.    
  27. // 测试类public class Client {  
  28.     public static void main(String[] args) {  
  29.         // 使用普通意义类  
  30.         Target concreteTarget = new ConcreteTarget();  
  31.         concreteTarget.request();  
  32.           
  33.         // 使用极度功效类,即适配类  
  34.         Target adapter = new Adapter();  
  35.         adapter.request();  
  36.     }  
  37. }  

澳门威尼斯人网址 21

// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee {
    public void specificRequest() {
        System.out.println("被适配类具有 特殊功能...");
    }
}

// 目标接口,或称为标准接口
interface Target {
    public void request();
}

// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {
    public void request() {
        System.out.println("普通类 具有 普通功能...");
    }
}

// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target{
    public void request() {
        super.specificRequest();
    }
}

// 测试类public class Client {
    public static void main(String[] args) {
        // 使用普通功能类
        Target concreteTarget = new ConcreteTarget();
        concreteTarget.request();

        // 使用特殊功能类,即适配类
        Target adapter = new Adapter();
        adapter.request();
    }
}

 

测试结果:

[java] view
plain

copy
print?澳门威尼斯人网址 22澳门威尼斯人网址 23

  1. 平日类 具有 普通意义…  
  2. 被适配类具有 特殊意义…  

澳门威尼斯人网址 24

普通类 具有 普通功能...
被适配类具有 特殊功能...

地点那种已毕的适配器称为类适配器,因为 艾达pter 类既两次三番了 Adaptee
(被适配类),也兑现了 Target 接口(因为
Java
不扶助多一而再,所以这么来落成),在 Client
类中大家可以依据需求选用并创制任一种符合需要的子类,来贯彻具体职能。其它一种适配器情势是指标适配器,它不是利用多三番五次或继续再落到实处的措施,而是利用直接关联,大概叫做委托的艺术,类图如下:

澳门威尼斯人网址 25

代码如下:

[java] view
plain

copy
print?澳门威尼斯人网址 26澳门威尼斯人网址 27

  1. // 适配器类,直接关乎被适配类,同时落成标准接口  
  2. class Adapter implements Target{  
  3.     // 直接关系被适配类  
  4.     private Adaptee adaptee;  
  5.       
  6.     // 可以通过构造函数传入具体须求适配的被适配类对象  
  7.     public Adapter (Adaptee adaptee) {  
  8.         this.adaptee = adaptee;  
  9.     }  
  10.       
  11.     public void request() {  
  12.         // 那里是运用委托的方法达成特殊作用  
  13.         this.adaptee.specificRequest();  
  14.     }  
  15. }  
  16.   
  17. // 测试类  
  18. public class Client {  
  19.     public static void main(String[] args) {  
  20.         // 使用普通意义类  
  21.         Target concreteTarget = new ConcreteTarget();  
  22.         concreteTarget.request();  
  23.           
  24.         // 使用尤其作用类,即适配类,  
  25.         // 要求先成立二个被适配类的靶子作为参数  
  26.         Target adapter = new Adapter(new Adaptee());  
  27.         adapter.request();  
  28.     }  
  29. }  

澳门威尼斯人网址 28

// 适配器类,直接关联被适配类,同时实现标准接口
class Adapter implements Target{
    // 直接关联被适配类
    private Adaptee adaptee;

    // 可以通过构造函数传入具体需要适配的被适配类对象
    public Adapter (Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    public void request() {
        // 这里是使用委托的方式完成特殊功能
        this.adaptee.specificRequest();
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        // 使用普通功能类
        Target concreteTarget = new ConcreteTarget();
        concreteTarget.request();

        // 使用特殊功能类,即适配类,
        // 需要先创建一个被适配类的对象作为参数
        Target adapter = new Adapter(new Adaptee());
        adapter.request();
    }
}

 

测试结果与地点的相同。从类图中大家也领会须求修改的只不过就是 Adapter
类的内部结构,即 Adapter
自个儿必须先拥有3个被适配类的目的,再把现实的相当规成效委托给那几个目的来贯彻。使用对象适配器方式,可以使得
Adapter 类(适配类)依据传入的 Adaptee
对象达到适配多个不一致被适配类的职能,当然,此时我们得以为多少个被适配类提取出三个接口或抽象类。那样看起来的话,如同目的适配器方式尤其灵敏一点。

  1. 格局总括

  5.1 优点

    5.1.1
通过适配器,客户端可以调用同一接口,由此对客户端的话是晶莹剔透的。那样做更简便易行、更直接、更严格。

    5.1.2 复用了留存的类,解决了现存类和复用环境须要不等同的难题。

    5.1.3
将目的类和适配者类解耦,通过引入2个适配器类重用现有的适配者类,而无需修改原有代码。

    5.1.4
二个目的适配器可以把六个例外的适配者类适配到同一个对象,约等于说,同多个适配器可以把适配者类和它的子类都适配到对象接口。

  5.2 缺点

    对于目的适配器来说,更换适配器的落到实处进程相比复杂。

  5.3 适用场景

    5.3.1 系统须要使用现有的类,而那一个类的接口不吻合系统的接口。

    5.3.2
想要建立3个得以采取的类,用于与部分互相之间没有太大关系的一些类,包罗一些大概在前几天引进的类一起工作。

    5.3.3 多个类所做的作业一样或貌似,然而全部分化接口的时候。

    5.3.4
旧的系统开发的类已经完毕了部分职能,然而客户端却不得不以此外接口的方式拜访,但我们不指望手动更改原有类的时候。

    5.3.5
使用第贰方组件,组件接口定义和自个儿定义的不比,不期待修改自个儿的接口,可是要动用第③方组件接口的职能。

  1. 适配器应用举例

  6.1
使用过ADO.NET的开发人士应该都用过Data艾达pter,它就是用作DataSet和数据源之间的适配器。DataAdapter通过映射Fill和Update来提供这一适配器。

  6.2 手机电源适配器

相关文章