95992828九五至尊2

设计形式之适配器格局,结构型形式八

一月 21st, 2019  |  882828九五至尊手机版

1 场景问题#

一、引出格局

1.1 装配电脑的事例##

  1. 旧的硬盘和电源

小李有一台老的台式电脑,硬盘实在是太小了,仅仅40GB,可是除了这一个题目外,整机性能还不易,甩掉不用太可惜了,于是决定去加装一块新的硬盘。

在装机公司为小李的微处理器加装新硬盘的时候,小李也在边缘观察,顺便明白点硬件知识。很快的,装机人士把两块硬盘都安装好了,细心的小李意识,那两块硬盘的连日格局是区其余。

透过装机人士的耐性讲解,小李搞精通了它们的两样。原先的硬盘是串口的,如图4.1,电脑电源如图4.2,那么连接电源的时候是直接连接。

旧的硬盘和处理器电源

  1. 出席新的硬盘

但是现在的新硬盘是并口的,如图4.3,电源的输出口不能直接连接到新的硬盘上了,于是就有了转接线,一边和电源的输出口连接,一边和新的硬盘电源输入口连接,解决了电源输出接口和硬盘输入接口不兼容的问题,如图4.4:

新的硬盘是并口的

  1. 有什么问题

若果把上边的题目抽象一下,用对象来讲述,那就是:有一个电源类和旧的硬盘类协作工作得很好,现在又有了一个新的硬盘类,现在想让新的硬盘类和电源类也合作使用,不过发现它们的接口无法合营,问题就生出了:怎样让原本的电源类的接口可以适应新的硬盘类的电源接口的急需吗?

  1. 何以缓解

缓解格局是使用一个倒车线类,转接线可以把电源的接口适配成为新的硬盘所急需的接口,那么这几个转接线类就接近本章的中流砥柱——适配器。

1.在世中的例子

1.2 同时帮衬数据库和文件的日志管理##

看了下面那几个例子,揣度对适配器形式有好几深感了。那是个在生活中常见的事例,类似的例子很多,比如:各个管道的转接头、分化制式的插座等等。可是那种例子只好救助大家知道适配器方式的效应,跟实际的施用系统开发连接有那个不一样,会倍感到类似是清楚了方式的法力,但是一到实在的系统开发中,就不驾驭怎么着使用那么些形式了,有些没有抓住主题的感觉。由此,上面依然以实际系统中的例子来叙述,以辅助大家真正清楚和利用适配器形式。

考虑一个记下日志的应用,由于用户对日记记录的须要很高,使得开发人士不可能大致的选取局地已有些日志工具或日志框架来满足用户的要求,而必要按照用户的渴求再一次开发新的日记管理种类。当然那里不容许完全按照实际系统这样去完整兑现,只是抽取跟适配器情势相关的部分来叙述。

  1. 日记管理首先版

在首先版的时候,用户须求日志以文件的款型记录。开发人员依据用户的渴求,对日记文件的存取已毕如下。

先容易定义日志对象,也就是讲述日志的对象模型,由于那个目标急需被写入文件中,由此这些目的需求体系化,示例代码如下:

/**
   * 日志数据对象
   */
public class LogModel implements Serializable  {
    /**
     * 日志编号
     */
    private String logId;
    /**
     * 操作人员
     */
    private String operateUser;
    /**
     * 操作时间,以yyyy-MM-dd HH:mm:ss的格式记录
     */
    private String operateTime;
    /**
     * 日志内容
     */
    private String logContent;

    public String getLogId() {
        return logId;
    }
    public void setLogId(String logId) {
        this.logId = logId;
    }
    public String getOperateUser() {
        return operateUser;
    }
    public void setOperateUser(String operateUser) {
        this.operateUser = operateUser;
    }
    public String getOperateTime() {
        return operateTime;
    }
    public void setOperateTime(String operateTime) {
        this.operateTime = operateTime;
    }
    public String getLogContent() {
        return logContent;
    }
    public void setLogContent(String logContent) {
        this.logContent = logContent;
    }
    public String toString() {
        return "logId="+logId+",operateUser="+operateUser+",operateTime="+operateTime+",logContent="+logContent;
    }
}

接下去定义一个操作日志文件的接口,示例代码如下:

/**
   * 日志文件操作接口
   */
public interface LogFileOperateApi {
    /**
     * 读取日志文件,从文件里面获取存储的日志列表对象
     * @return 存储的日志列表对象
     */
    public List<LogModel> readLogFile();
    /**
     * 写日志文件,把日志列表写出到日志文件中去
     * @param list 要写到日志文件的日志列表
     */
    public void writeLogFile(List<LogModel> list);
}

贯彻日志文件的存取,现在的兑现也很不难,就是读写文件,示例代码如下:

/**
   * 实现对日志文件的操作
   */
public class LogFileOperate implements LogFileOperateApi{
    /**
     * 日志文件的路径和文件名称,默认是当前项目的根下的AdapterLog.log
     */
    private String logFilePathName = "AdapterLog.log";
    /**
     * 构造方法,传入文件的路径和名称
     * @param logFilePathName 文件的路径和名称
     */
    public LogFileOperate(String logFilePathName) {
        //先判断是否传入了文件的路径和名称,如果是,
        //就重新设置操作的日志文件的路径和名称
        if(logFilePathName!=null && logFilePathName.trim().length()>0){
            this.logFilePathName = logFilePathName;
        }
    }
    public  List<LogModel> readLogFile() {
        List<LogModel> list = null;
        ObjectInputStream oin = null;
        try {
            File f = new File(logFilePathName);
            if(f.exists()) {
                oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
                list = (List<LogModel>)oin.readObject();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(oin!=null) {
                    oin.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return list;
    }
    public void writeLogFile(List<LogModel> list){
        File f = new File(logFilePathName);
        ObjectOutputStream oout = null;
        try {
            oout = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
            oout.writeObject(list);        
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

写个客户端来测试一下,看看好用不,示例代码如下:

public class Client {
    public static void main(String[] args) {
        //准备日志内容,也就是测试的数据
        LogModel lm1 = new LogModel();
        lm1.setLogId("001");
        lm1.setOperateUser("admin");
        lm1.setOperateTime("2010-03-0210:08:18");
        lm1.setLogContent("这是一个测试");

        List<LogModel> list = new ArrayList<LogModel>();
        list.add(lm1);
        //创建操作日志文件的对象
        LogFileOperateApi api = new LogFileOperate("");
        //保存日志文件
        api.writeLogFile(list);

        //读取日志文件的内容
        List<LogModel> readLog = api.readLogFile();
        System.out.println("readLog="+readLog);
    }
}

迄今停止就概括的兑现了用户的渴求,把日记保存到文件中,并能从文件中把日志内容读取出来,举办田间管理。看上去很不难,对啊,别慌,接着来。

  1. 日志管理第二版

用户使用日志管理的首先版一段时间过后,开端考虑升级系统,操纵要动用数据库来管理日志,很快,按照数据库的日记管理也兑现出来了,并定义了日志管理的操作接口,首假使对准日志的增删改查方法,接口的以身作则代码如下:

/**
   * 定义操作日志的应用接口,为了示例的简单,只是简单的定义了增删改查的方法
   */
public interface LogDbOperateApi {
    /**
     * 新增日志
     * @param lm 需要新增的日志对象
     */
    public void createLog(LogModel lm);
    /**
     * 修改日志
     * @param lm 需要修改的日志对象
     */
    public void updateLog(LogModel lm);
    /**
     * 删除日志
     * @param lm 需要删除的日志对象
     */
    public void removeLog(LogModel lm);
    /**
     * 获取所有的日志
     * @return 所有的日志对象
     */
    public List<LogModel> getAllLog();
}

对于使用数据库来保存日志的兑现,那里就不去涉及了,反正知道有诸如此类一个落到实处就足以了。

客户指出了新的需要,能无法让日志管理的第二版,达成同时支持数据库存储和文件存储二种方法?

大家都是领略,天朝家用电器的业内电压和美利坚同盟国规范电压是不相同的,也就是我们把在天朝买的记录本带到花旗国去也不知所可正常使用的,想要使用就非得跟换电源适配器。将电源适配器的电压换成适应米国的电压就可以正常使用的。而以此电源适配器的效用就是本篇所要学习的主旨“适配器(艾达(Ada)pter)”。

1.3 有什么问题##

有情侣可能会想,那有如何困难的吧,二种完成格局不是都早就落到实处了的啊,合并起来不就可以了?

题目就在于,现在的作业是应用的第二版的接口,直接运用第二版新投入的贯彻是不曾问题的,第二版新参与了保留日志到数据库中;只是对于已部分达成格局,也就是在率先版中使用的文件存储的主意,它的操作接口和第二版分歧,那就导致现在的客户端,无法以同等的章程来一贯利用第一版的完成,如下图4.5所示:

没辙协作第一版的接口示意图

那就象征,要想同时接济文件和数据库存储二种格局,须要再附加的做一些行事,才可以让第一版的完成适应新的作业的内需。

兴许有朋友会想,干脆按照第二版的接口要求重新完成一个文书操作的靶子不就足以了,那样真的可以,但是何要求再次做已经达成的效益吗?有道是要想方法复用,而不是双重完毕

一种很不难想到的格局是平昔改动已有些第一版的代码。那种方法是不太好的,若是直白改动了第一版的代码,那么可能会导致其他着重于那几个落成的应用不可以健康运行,再说,有可能首先版和第二版的付出公司是分裂的,在其次版达成的时候,根本拿不到第一版的源代码。

那就是说该如何来已毕啊?

2.软件实例

2 解决方案#

譬如现在要做一个日记记录的连串,在首先版中使用文件为载体,可以对日记文件举办增产和删除,随着事情的伸张,文件日志已力不从心满足须要,于是在其次版中选择了数据库来囤积日志,并能对日记进行CRUD。由于客户的变异,现在告诉您,这二种成效都要。

2.1 适配器形式来解决##

用来缓解上述问题的一个理所当然的解决方案就是适配器格局。那么如何是适配器情势呢?

  1. 适配器形式定义

适配器方式定义

  1. 使用适配器形式来缓解的笔触

精心分析下面的题目,问题的源于在于接口的不般配,功效是宗旨落到实处了的,也就是说,只要想方法让两边的接口匹配起来,就可以复用第一版的功效了。

安分守己适配器格局的落实方式,可以定义一个类来落到实处第二版的接口,然后在里头贯彻的时候,转调第一版已经落到实处了的出力,那样就足以由此对象组合的不二法门,既复用了第一版已有些职能,同时又在接口上满意了第二版调用的要求。达成上述工作的那一个类就是适配器。

题目应运而生了,就是第二版中定义的接口,对第一版并不是适用,也就是说,现在的客户端不能以同等的艺术平素选用第一版。想要客户端同时选拔那两种,就必须附加的做些工作。

2.2 格局结构和表达##

适配器方式的布局如图所示:

适配器格局的结构图

Client:客户端,调用自己要求的小圈子接口Target。

Target:定义客户端须求的跟特定领域有关的接口。

Adaptee:已经存在的接口,寻常能满意客户端的效率要求,只是接口与客户端必要的一定领域接口分歧,需求被适配

Adapter:适配器,把艾达(Ada)ptee适配成为Client须求的Target。

有那般两种缓解方案:1.在其次版中再一次在写一个首先版的功能,那就是一对一于白做了,明明已经有现成的了,还要再重写。2.修改第一版,迫使第一版和第二版达成包容共识,然而首先版不是你付出的或者第一版看重了恒河沙数项目,那怎么做?

2.3 适配器方式示例代码##

  1. 先看看Target接口的概念,示例代码如下:

/**
   * 定义客户端使用的接口,与特定领域相关
   */
public interface Target {
    /**
     * 示意方法,客户端请求处理的方法
     */
    public void request();
}
  1. 再看看须求被适配的靶子定义,示例代码如下:

/**
   * 已经存在的接口,这个接口需要被适配
   */
public class Adaptee {
    /**
     * 示意方法,原本已经存在,已经实现的方法
     */
    public void specificRequest() {
        //具体的功能处理
    }
}
  1. 再看看适配器对象的骨干完成,示例代码如下:

/**
   * 适配器
   */
public class Adapter implements Target {
    /**
     * 持有需要被适配的接口对象
     */
    private Adaptee adaptee;
    /**
     * 构造方法,传入需要被适配的对象
     * @param adaptee 需要被适配的对象
     */
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    public void request() {
        //可能转调已经实现了的方法,进行适配
        adaptee.specificRequest();
    }
}
  1. 再来看看使用适配器的客户端,示例代码如下:

/**
   * 使用适配器的客户端
   */
public class Client {  
    public static void main(String[] args) {
        // 创建需被适配的对象
        Adaptee adaptee = new Adaptee();
        // 创建客户端需要调用的接口对象
        Target target = new Adapter(adaptee);
        // 请求处理
        target.request();
    }
}

二、认识格局

2.4 使用适配器格局来贯彻示例##

要运用适配器方式来落实示例,关键就是要兑现那一个适配器对象,它需求贯彻第二版的接口,可是在里头贯彻的时候,须要调用第一版已经落到实处的意义。也就是说,其次版的接口就一定于适配器格局中的Target接口,而首先版已有的完毕就一定于适配器方式中的Adaptee对象

  1. 把这些适配器不难的兑现出来,示意一下,示例代码如下:

/**
   * 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
   */
public class Adapter implements LogDbOperateApi{
    /**
     * 持有需要被适配的接口对象
     */
    private LogFileOperateApi adaptee;
    /**
     * 构造方法,传入需要被适配的对象
     * @param adaptee 需要被适配的对象
     */
    public Adapter(LogFileOperateApi adaptee) {
        this.adaptee = adaptee;
    }

    public void createLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = adaptee.readLogFile();
        //2:加入新的日志对象
        list.add(lm);
        //3:重新写入文件
        adaptee.writeLogFile(list);
    }
    public List<LogModel> getAllLog() {
        return adaptee.readLogFile();
    }
    public void removeLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = adaptee.readLogFile();
        //2:删除相应的日志对象
        list.remove(lm);
        //3:重新写入文件
        adaptee.writeLogFile(list);
    }
    public void updateLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = adaptee.readLogFile();
        //2:修改相应的日志对象
        for(int i=0;i<list.size();i++){
            if(list.get(i).getLogId().equals(lm.getLogId())){
                list.set(i, lm);
                break;
            }
        }
        //3:重新写入文件
        adaptee.writeLogFile(list);
    }
}
  1. 此时的客户端也需求有些变动,示例代码如下:

public class Client {
    public static void main(String[] args) {
        //准备日志内容,也就是测试的数据
        LogModel lm1 = new LogModel();
        lm1.setLogId("001");
        lm1.setOperateUser("admin");
        lm1.setOperateTime("2010-03-0210:08:18");
        lm1.setLogContent("这是一个测试");
        List<LogModel> list = new ArrayList<LogModel>();
        list.add(lm1);
        //创建操作日志文件的对象
        LogFileOperateApi logFileApi = new LogFileOperate("");

        //创建新版的操作日志的接口对象
        LogDbOperateApi api = new Adapter(logFileApi);

        //保存日志文件
        api.createLog(lm1);    
        //读取日志文件的内容
        List<LogModel> allLog = api.getAllLog();
        System.out.println("allLog="+allLog);
    }
}
  1. 小结一下思路

① 原有文件存取日志的点子,运行得很好,如图所示:

原来文件存取日志的法门

② 现在有了新的按照数据库的贯彻,新的贯彻有温馨的接口,如图所示:

新的基于数据库的兑现


现在想要在第二版的完成里面,可以同时同盟第一版的作用,那么就应有一个类来落到实处第二版的接口,然后在这几个类里面去调用已有些职能达成,那几个类就是适配器,如下图所示:

投入适配器的落实社团示意图

地方是分步的思绪,现在来看一下前方示例的一体化布局,如图所示:

适配器落成的示范的构造示意图

有如上边的例子,原本新的日志操作接口不可以和旧的文书落到实处同台工作,不过通过适配器适配后,新的日志操作接口就能和旧的公文落到实处日志存储一起干活了。

882828九五至尊手机版,1.适配器情势定义

3 格局教学#

将一个已存在类的接口转化成客户愿意的另一个接口。适配器方式使得原本由于接口不包容问题而不能共同坐班的那个类可以同步坐班。

3.1 认识适配器情势##

  1. 方式的效劳

适配器格局的根本成效是举行转移匹配,目标是复用已有的效益,而不是来贯彻新的接口。也就是说,客户端须求的机能应该是早就落实好了的,不需求适配器形式来兑现,适配器方式主要承担把不包容的接口转换成客户端期望的榜样就好了

但那并不是说,在适配器里面就不可能促成效益,适配器里面可以完毕效益,称那种适配器为智能适配器。再说了,在接口匹配和转移的长河中,也是有可能需求极度完毕自然的法力,才可以转移过来的,比如需求调整参数以开展匹配等。

  1. Adaptee和Target的关系

适配器方式中被适配的接口Adaptee和适配成为的接口Target是从未涉及的,也就是说,Adaptee和Target中的方法既能够等效,也可以分裂,极端气象下七个接口里面的法子也许是完全不一样的,当然最好气象下也足以完全相同。

此处所说的一模一样和见仁见智,是指的章程定义的称呼、参数列表、再次来到值、包涵方法本身的机能都足以等效和见仁见智。

  1. 目的组合

基于前边的落实,你会发觉,适配器的兑现格局实际上是器重对象组合的主意。通过给适配器对象组合被适配的对象,然后当客户端调用Target的时候,适配器会把相应的作用,委托给被适配的靶子去做到。

瞩目定义中的多少个主要字“已存在类”,“转化”.

3.2 适配器情势的达成##

  1. 适配器的宽泛完成

在促成适配器的时候,适配器平时是一个类,一般会让适配器类去已毕Target接口,然后在适配器的求实贯彻里面调用Adaptee。也就是说适配器寻常是一个Target类型,而不是Adaptee类型。如同前边的例证演示的那么。

  1. 智能适配器

在实际费用中,适配器也得以兑现部分Adaptee没有落到实处,但是在Target中定义的作用,那种景况就需求在适配器的兑现里面,参与新效率的贯彻,那种适配器被誉为智能适配器。

若是要运用智能适配器,一般新进入的功力的完成,会用到很多艾达ptee的出力,相当于采纳Adaptee的效果来促成更高层的效率。当然也可以完全完毕新加的效应,跟已有些效益都不靠边,变相是扩充了效果。

  1. 适配多少个Adaptee

适配器在适配的时候,可以适配多少个Adaptee,也就是说完结某个新的Target的成效的时候,须要调用到多少个模块的功用,适配多个模块的效率才能知足新接口的渴求

  1. 适配器艾达(Ada)pter落成的复杂程度

适配器艾达(Ada)pter完毕的复杂程度,取决于Target和Adaptee的一般程度。

假定相似程度很高,比如唯有方法名称不平等,那么Adapter只是索要不难的转调一下接口就好了。

一旦相似程度低,比如两边接口的方法定义的功能完全差别等,在Target中定义的一个艺术,可能在艾达ptee中定义了多个更小的点子,那么这么些时候在促成艾达(Ada)pter的时候,就必要结合调用了。

  1. 缺省适配

缺省适配的意味是:为一个接口提供缺省兑现。有了它,就无须直接去贯彻接口,而是应用继承那个缺省适配对象,从而让子类能够有拔取的去掩盖完结内需的主意,对于不须要的章程,就利用缺省适配的措施就可以了。

2.化解方案

3.3 双向适配器##

适配器也可以落成双向的适配,前方大家讲的都是把艾达ptee适配成为Target,其实也得以把Target适配成为Adaptee,也就是说那么些适配器可以同时当作Target和艾达ptee来选用

持续前边讲述的事例,假若说由于一些原因,第一版和第二版会同时共存一段时间,比如第二版的使用还在频频调整中,也就是第二版还不够稳定。客户指出,希望在两版共存期间,主要如故在采纳第一版,同时愿意第一版的日志也能记入到数据库中,也就是客户固然操作的接口是率先版的日记接口,界面也是第一版的界面,不过可以动用第二版的把日记记录到数据库的机能。

也就是说希望两版能促成双向的适配,结构如下图所示:

双向适配结构图

此处只加了多少个新的事物,一个就是DB存储日志的贯彻,后面的事例是从未的,因为一向被适配成选用文件存储日志的完结了;其它一个就是双向适配器,其实与把公文存储的点子适配成为DB已毕的接口是同等的,只要求新加上把DB已毕的机能适配成为文件贯彻的接口就好了。

  1. 先看看DB存储日志的完结,为了不难,那里就不去真正落到实处和数据库交互了,示意一下,示例代码如下:

/**
   * DB存储日志的实现,为了简单,这里就不去真的实现和数据库交互了,示意一下
   */
public class LogDbOperate implements LogDbOperateApi{
    public void createLog(LogModel lm) {
        System.out.println("now in LogDbOperate createLog,lm="+lm);
    }
    public List<LogModel> getAllLog() {
        System.out.println("now in LogDbOperate getAllLog");
        return null;
    }
    public void removeLog(LogModel lm) {
        System.out.println("now in LogDbOperate removeLog,lm="+lm);
    }
    public void updateLog(LogModel lm) {
        System.out.println("now in LogDbOperate updateLog,lm="+lm);
    }
}
  1. 接下来看看新的适配器的兑现,由于是双向的适配器,一个大方向是:把新的DB达成的接口适配成为旧的文书操作要求的接口;其它一个趋势是把旧的公文操作的接口适配成为新的DB完毕内需的接口。示例代码如下:

/**
   * 双向适配器对象
   */
public class TwoDirectAdapter implements LogDbOperateApi,LogFileOperateApi{
    /**
     * 持有需要被适配的文件存储日志的接口对象
     */
    private LogFileOperateApi fileLog;
    /**
     * 持有需要被适配的DB存储日志的接口对象
     */
    private LogDbOperateApi  dbLog;
    /**
     * 构造方法,传入需要被适配的对象
     * @param fileLog 需要被适配的文件存储日志的接口对象
     * @param dbLog 需要被适配的DB存储日志的接口对象
     */
    public TwoDirectAdapter(LogFileOperateApi fileLog, LogDbOperateApi dbLog) {
        this.fileLog = fileLog;
        this.dbLog = dbLog;
    }
    /*-----以下是把文件操作的方式适配成为DB实现方式的接口-----*/
    public void createLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = fileLog.readLogFile();
        //2:加入新的日志对象
        list.add(lm);
        //3:重新写入文件
        fileLog.writeLogFile(list);
    }
    public List<LogModel> getAllLog() {
        return fileLog.readLogFile();
    }
    public void removeLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = fileLog.readLogFile();
        //2:删除相应的日志对象
        list.remove(lm);
        //3:重新写入文件
        fileLog.writeLogFile(list);
    }
    public void updateLog(LogModel lm) {
        //1:先读取文件的内容
        List<LogModel> list = fileLog.readLogFile();
        //2:修改相应的日志对象
        for(int i=0;i<list.size();i++){
            if(list.get(i).getLogId().equals(lm.getLogId())){
                list.set(i, lm);
                break;
            }
        }
        //3:重新写入文件
        fileLog.writeLogFile(list);
    }
    /*-----以下是把DB操作的方式适配成为文件实现方式的接口-----*/
    public List<LogModel> readLogFile() {
        return dbLog.getAllLog();
    }
    public void writeLogFile(List<LogModel> list) {
        //1:最简单的实现思路,先删除数据库中的数据
        //2:然后循环把现在的数据加入到数据库中
        for(LogModel lm : list){
            dbLog.createLog(lm);
        }    
    }
}
  1. 看看哪些使用那些双向适配器,测试一下,示例代码如下:

public class Client {
    public static void main(String[] args) {
        //准备日志内容,也就是测试的数据
        LogModel lm1 = new LogModel();
        lm1.setLogId("001");
        lm1.setOperateUser("admin");
        lm1.setOperateTime("2010-03-0210:08:18");
        lm1.setLogContent("这是一个测试");
        List<LogModel> list = new ArrayList<LogModel>();
        list.add(lm1);
        //创建操作日志文件的对象
        LogFileOperateApi fileLogApi = new LogFileOperate("");
        LogDbOperateApi dbLogApi = new LogDbOperate();

        //创建经过双向适配后的操作日志的接口对象
        LogFileOperateApi fileLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi);
        LogDbOperateApi dbLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi);

        //先测试从文件操作适配到第二版,
        //虽然调用的是第二版的接口,其实是文件操作在实现
        dbLogApi2.createLog(lm1);
        List<LogModel> allLog = dbLogApi2.getAllLog();
        System.out.println("allLog="+allLog);

        //再测试从数据库存储适配成第一版的接口,
        //也就是调用第一版的接口,其实是数据实现
        fileLogApi2.writeLogFile(list);
        fileLogApi2.readLogFile();
    }
}

骨子里,使用适配器有一个私房的题目,就是被适配的靶子不再包容艾达ptee的接口,因为适配器只是落成了Target的接口,那造成并不是有着艾达ptee对象可以被使用的地方都得以行使适配器

而双向适配器就缓解了那般的题材,双向适配器同时完毕了Target和Adaptee的接口,使得双向适配器能够在Target或Adaptee被应用的地方选取,以提供对富有客户的透明性,尤其在三个例外的客户需求用分化的法子查看同一个对象时,适合采纳双向适配器。

该问题的发源就是新老系统接口不匹配,具体的意义都已落实,现在要做的就是是那三个接口匹配起来。

3.4 适配器情势的利弊##

  1. 更好的复用性

假定效果是已经有了的,只是接口不匹配,那么通过适配器方式就可以让那些职能博得更好的复用。

  1. 更好的可扩充性

在落实适配器作用的时候,可以调用自己花费的效应,从而自然的伸张系统的效益。

  1. 过多的行使适配器,会让系统充裕混乱,不便于全部举办把握

诸如:明明看到调用的是A接口,其实其中被适配成了B接口来促成,一个连串一旦太多那种场合,无异于一场灾害。因而借使不是很有必不可少,可以不应用适配器,而是径直对系统举行重构。

大家能够定义一个类来落到实处新连串的接口,然后在中间贯彻时转调老系统已经落到实处的机能,那样经过对象组合的措施,既复用了老系统的已有些职能,又在接口上满意新种类调用的渴求。

3.5 思考适配器方式##

  1. 适配器情势的精神

适配器形式的本色:转换匹配,复用功用。

适配器通过更换调用已有的达成,从而能把已部分完成匹配成须求的接口,使之能满意客户端的急需。也就是说转换匹配是一手,而复用已有些效益才是目的。

在展开转换匹配的长河中,适配器还能在转移调用的内外促成部分功力处理,也就是兑现智能的适配。

  1. 曾几何时接纳适配器形式

提议在如下意况中,采纳适配器方式:

如若你想要使用一个已经存在的类,不过它的接口不吻合您的须要,那种气象可以动用适配器方式,来把已有的完毕转换成你须求的接口

若是您想创制一个足以复用的类,那一个类可能和有些不兼容的类一起工作,那种场所可以动用适配器情势,到时候须要什么样就适配什么

假定您想利用一些早已存在的子类,不过不容许对每一个子类都进展适配,那种情景可以拔取对象适配器,直接适配这一个子类的父类就可以了。

3.形式原型

3.6 相关方式##

  1. 适配器格局与桥接情势

实际那两个形式除了结构略为一般外,成效上完全两样。

适配器格局是把七个或者多个接口的听从举行转换匹配;而桥接形式是让接口和落到实处部分相分离,以便它们可以相对独立的变迁。

  1. 适配器情势与点缀形式

从某种意义上讲,适配器方式能模拟完结简单的点缀形式的成效,也就是为已有功能扩充功效。比如我们在适配器里面这么写:

public void adapterMethod(){
      System.out.println("在调用Adaptee的方法之前完成一定的工作");
      //调用Adaptee的相关方法
      adaptee.method();
      System.out.println("在调用Adaptee的方法之后完成一定的工作");
}

如上的写法,就相当于在调用艾达(Ada)ptee的被适配方法前后添加了新的效果,这样适配过后,客户端得到的功用就不单单是艾达ptee的被适配方法的效应了。看看是否相近装饰情势的效益吗?

专注,仅仅是相仿,造成那种近似的案由:三种设计方式在贯彻上都是使用的靶子组合,都得以在转调组合对象的职能前后开展一些附加的处理,由此有如此一个相似性。它们的目标和实质都是不一样的

五个形式有一个很大的两样:貌似适配器适配过后是内需变更接口的,假如不改接口就从不要求适配了;而装修形式是不改接口的,无论多少层装饰都是一个接口。因而装饰方式可以很容易的支撑递归组合,而适配器就做不到了,每趟的接口不一样,没办法递归。

  1. 适配器方式和代理格局

适配器情势可以跟代理方式组合使用,在促成适配器的时候,能够因而代理来调用艾达(Ada)ptee,那足以获取更大的油滑。

  1. 适配器形式和抽象工厂形式

在适配器达成的时候,一般而言需求取得被适配的对象,固然被适配的是一个接口,那么就足以组成一些足以创制对象实例的设计形式,来得到被适配的对象示例。比如:抽象工厂形式、单例情势、工厂方法格局等等。

 882828九五至尊手机版 1

Clint:客户端,调用自己索要的圈子接口Itarget。

ITarget:定义客户端必要的与相关领域特定的接口。

艾达(Ada)ptee:已存在的接口,平常能满足客户端的职能必要,不过接口与客户端须要的一定领域接口不一样等,需求被适配。

艾达(Ada)pter:适配器,把艾达(Ada)ptee适配成为Client须要的ITarget。

4.形式原型代码示例

class Program
    {
        static void Main(string[] args)
        {
            //创建需要被适配的对象
            Adaptee adaptee = new Adaptee();
            //创建客户端需要调用的接口对象
            ITarget traget = new Adapter(adaptee);
            //请求处理
            traget.Request();
            Console.ReadKey();
        }
    }


    public interface ITarget
    {
        void Request();
    }

    /// <summary>
    /// 已存在的接口,需要被适配
    /// </summary>
    public class Adaptee
    {
        public void SpecificRequest()
        {
            Console.WriteLine("调用老系统功能");
        }
    }

    /// <summary>
    /// 适配器
    /// </summary>
    public class Adapter : ITarget
    {
        /// <summary>
        /// 持有需要被适配的接口对象
        /// </summary>
        Adaptee adaptee = null;

        /// <summary>
        /// 构造函数,传入需要被适配的对象
        /// </summary>
        /// <param name="adaptee"></param>
        public Adapter(Adaptee adaptee)
        {
            this.adaptee = adaptee;
        }

        public void Request()
        {
            //转调
            adaptee.SpecificRequest();
        }
    }

 

 三、精晓格局

1.情势成效:

适配器形式紧要成效是开展转向匹配,目的是复用现有的成效,而不是落到实处新的接口。也就是说,客户端须求的职能是早已落实好了的,不须求适配器形式来完毕,适配器形式首要承担把不合作的接口转化成客户端期望的规范就行了。

那并不是说,在适配器里面无法达成效益。适配器里面可以兑现效益,那种适配器叫做“智能适配器”,再说了,在适配的长河中,也有部分索要一定的机能,才能更换过来,比如调整参数以拓展适配。

2.Adaptee和ITarget的关系

适配器方式中被适配的接口Adaptee和适配成为的接口ITarget是绝非涉嫌的,也就是说,Adaptee和ITarget中的方法既可以同样,也足以不相同。

3.对像组合

适配器的落成方式实在是依靠对象组合的不二法门。通过给适配器对象组合被被适配的靶子,让后当客户端调用ITarget时,适配器会把相应的功效委托给被适配的目的去达成。

4.适配器的宽泛已毕

在促成适配器完结时,适配器平时是一个类,一般会让适配器类去完毕ITarget接口,让后在适配器的现实贯彻里面调用艾达(Ada)ptee。也就是说适配器常常是一个ITarget类型,而不是艾达ptee类型。

5.智能适配器

在支付中,适配器也可以完结部分Adaptee没有的功用,但在ITarget定义了这几个意义。这种景观就须求在适配器的落成里面,参预新功能的兑现。那种适配器被改成智能适配器。

6.适配五个艾达(Ada)ptee

适配器在适配时,能够适配多个艾达(Ada)ptee,也就是说完成某个新的ITarget的作用时,需要调用多个模块的职能,那样才能满意新接口的渴求。

7.缺省适配

缺省适配,就是为一个接口提供缺省落到实处。有了它,就可以毫不间接去已毕接口,而是利用继承那么些缺省适配对象。

 8.双向适配器:

原先都是把Adaptee适配成为ITarget,其实也得以将ITarget适配成为艾达(Ada)ptee。也就是说那些适配器类可以看做ITarget用也足以用作艾达(Ada)ptee用。

利用适配器有一个隐秘问题,就是被适配的对象不再包容艾达ptee的接口,应为适配器只是落成了ITarget接口。那造成并不是颇具的艾达(Ada)ptee对象足以选拔的地方都能运用适配器。

双向适配器就解决了这么的题材,双向适配器同时落实了ITarget和艾达(Ada)ptee的接口,使得双向适配器可以在ITarget或Adaptee被采用的地点选用,以提供对说有客户的透明性。

9.对象适配器

目的适配器的兑现依靠于对象组合,就如前边示例的。

10.类适配器

类适配器的兑现利用多重继承对一个接口与另一个接口举办匹配。

11.对象适配器和类适配器的衡量

类适配器使用对象继承,是静态定义格局;对象适配器使用对象组合,是动态构成方式。

12.适配器方式的亮点

1)  更好的复用性

2)  更好的可扩充性

13.适配器模型的老毛病

重重地选取适配器,会让系统尤其混乱,不不难对全部的握住。

14.哪一天拔取适配器情势

1)  系统要求运用现有的类,而此类的接口不合乎系统的内需。

2) 
想要建立一个方可重复使用的类,用于与部分互相之间没有太大关系的部分类,包罗一些也许在后天引进的类一起工作。这一个源类不必然有很复杂的接口。

3) 
(对目的适配器而言)在规划里,须求改变五个已有子类的接口,如若应用类的适配器形式,就要指向每一个子类做一个适配器,而那不太实在。

15.小结

适配器格局的本来面目是:转化匹配,复用功用。

适配器通过转调已有的落成,从而能把已部分完结匹配成为亟待的接口,使之能知足客户端的内需。也就是说转换匹配是一手,而复用成效才是目的。

相关文章

Your Comments

近期评论

    功能


    网站地图xml地图