设计模式思考之适配器模式

适配器模式的定义

将一个类的接口,转换成客户所期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

适配器模式.png

这里有四个名词,需要理解一下

Client: 客户端,调用自己需要领域的接口Target

Target: 定义客户端需要的跟特定领域相关的接口

Adaptee: 已经存在的接口,但是与客户端所需领域的接口要求不一致,需要被适配(例如是方法名字不一致)

Adapter: 适配器,把Adaptee适配成Client需要的Target

Example1

Adaptee.java

package com.lhsjohn.adapter.example1;
/**
 * 已经存在的接口,这个接口需要被适配
 * @author lihuashuo
 *
 */
public class Adaptee {
 
    /**
     * 示意方法,原本已经存在,已经实现了的方法
     */
    
    public void specificRequest() {
        //具体的功能处理
    }
    
}



Target.java


package com.lhsjohn.adapter.example1;
/**
 * 定义客户端使用的接口,与特定领域相关
 * @author lihuashuo
 *
 */
public interface Target {

    /**
     * 示意方法,客户端请求处理的方法
     */
    
    public void request();
    
    
}

Adapter.java

package com.lhsjohn.adapter.example1;
/*
 * 适配器
 */
public class Adapter implements Target{

   //持有需要被适配的接口对象
    private Adaptee adaptee;

    /**
     * 构造方法,传入需要被适配的对象
     * @param adaptee
     */
    public Adapter(Adaptee adaptee) {
          this.adaptee = adaptee;
   }
    
    public void request() {
        //可能转调已经实现了的方法,进行适配
        adaptee.specificRequest();
    }
    
}



Client.java

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


下面是对该模式的一个扩展

有这样一个应用场景:

同时支持数据库和文件的日志管理

1:日志管理第一版:

在第一版的时候,用户要求日志以文件的形式记录

2:日志管理第二版:
要采用数据可以来管理日志,并且已有新的基于数据库的实现

3:现在想在第二版的实现里面,能够同时兼容第一版的功能,那么就应该有一个类来实现第二版的接口,然后在这个类里面去调用已有的功能实现,这个类就是适配器

Example2

LogModel.java

package com.lhsjohn.adapter.example2;

import java.io.Serializable;

/**
 * 日志数据对象
 * @author lihuashuo
 *
 */
public class LogModel implements Serializable {

    //日志编号
    private String logId;
    //操作人员
    private String operaterUser;
    //操作时间,以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 getOperaterUser() {
        return operaterUser;
    }
    public void setOperaterUser(String operaterUser) {
        this.operaterUser = operaterUser;
    }
    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;
    }
    @Override
    public String toString() {
        return "LogModel [logId=" + logId + ", operaterUser=" + operaterUser + ", operateTime=" + operateTime
                + ", logContent=" + logContent + "]";
    }
    
    
    
    
    
}



LogFileOperateApi.java


package com.lhsjohn.adapter.example2;

import java.util.List;

/**
 * 日志文件操作接口
 * @author lihuashuo
 *
 */
public interface LogFileOperateApi {

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



LogFileOperate.java



package com.lhsjohn.adapter.example2;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
 * 实现对日志文件的操作
 * @author lihuashuo
 *
 */
public class LogFileOperate implements LogFileOperateApi {

    //日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log
    private String logFilePathName="AdapterLog.log";
    
    /**
     * 构造方法,传入文件的路径和名称
     * @param logFilePathName 文件的路径和名称
     */
    public LogFileOperate(String logFilePathName) {
        //先判断是否传入了文件的路径和名称
        //如果是的话,就重新设置操作的日志文件的路径和名称
        if(logFilePathName!=null&& logFilePathName.trim().length()>0) {         
            this.logFilePathName = logFilePathName;
        }
    }

    @Override
    public List readLogFile() {
       List list=null;
       ObjectInputStream oin=null;
       
       try {
       File f=new File(logFilePathName);
       if(f.exists()) {
            oin=new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
            list=(List) oin.readObject();     
           }
       
       }catch (Exception e) {
           e.printStackTrace();
        }finally {
            try {
                if(oin!=null) {
                    oin.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
       
        return list;
    }

    @Override
    public void writeLogFile(List list) {
       File f=new File(logFilePathName);
       ObjectOutputStream oout=null;
       try {
        oout=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
        oout.writeObject(list);
       } catch (Exception e) {
          e.printStackTrace();
      }finally {
          try {
             if(oout!=null) {
                 oout.close();
                }
              } catch (Exception e) {
                e.printStackTrace();
            }
          }
      }

    }

LogDbOperator.java


package com.lhsjohn.adapter.example2;

import java.util.List;

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



Adapter.java


package com.lhsjohn.adapter.example2;

import java.util.List;
/**
 * 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
 * @author lihuashuo
 *
 */
public class Adapter implements LogDbOperaterApi {

    /**
     * 持有需要被适配的对象
     */
    
    private LogFileOperateApi adaptee;
    
    /**
     * 构造方法,传入需要被适配的对象
     * @param adaptee 需要被是配的对象
     */
    public Adapter(LogFileOperateApi adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void createLog(LogModel lm) {
        //1:先读取文件的内容
        List list = adaptee.readLogFile();
        //2:加入新的日志对象
        list.add(lm);
        //3.重新写入文件
        adaptee.writeLogFile(list);
    }

    @Override
    public void updateLog(LogModel lm) {
        //1:先读取文件的内容
        List list = adaptee.readLogFile();
        //2:修改相应的日志对象
        for(int i=0;i list = adaptee.readLogFile();
        //2:删除对应的日志对象
        list.remove(lm);
        //3:重新写入文件
        adaptee.writeLogFile(list);

    }

    @Override
    public List getAllLog() {
        return adaptee.readLogFile();
    }

}




Client.java



package com.lhsjohn.adapter.example2;

import java.util.ArrayList;
import java.util.List;

public class Client {
   
    public static void main(String[] args) {
        //准备日志内容,也就是测试的数据
        LogModel lm1=new LogModel();
        lm1.setLogId("001");
        lm1.setOperaterUser("lhsjohn");
        lm1.setOperateTime("2018-08-24 00:30:21");
        lm1.setLogContent("这是一个测试");
        
        List list=new ArrayList();
        list.add(lm1);
        
        //创建操作日志文件的对象
        LogFileOperateApi logFileApi=new LogFileOperate("");
        
        //创建新版的操作日志的接口对象
        LogDbOperaterApi api=new Adapter(logFileApi);
        
        //保存日志文件
        api.createLog(lm1);
        //未使用适配器之前的:api.writeLogFile(list);
        
        //读取日志文件的内容
        //未使用适配器的:List list2 = api.readLogFile();
        //使用适配器的
        List list2 = api.getAllLog();
        System.out.println("readLog= "+list2);
        
        
    }
}



再此基础上,再重新认识一下适配器模式

1:适配器模式的功能

主要是进行转换匹配,目的是复用已有的功能,而不是实现新的接口。

适配器里面也可以实现功能,称这种为智能适配器。

2:Adaptee和Target的关系

适配器模式中被适配的接口Adaptee和适配成为的接口Target是没有关联的。

3:对象组合

适配器的实现方式其实是依靠的对象组合的方式
其实也可以用对象继承的方式,但是不推荐,因为Java是单继承的,只能继承一个类,如果要使用多个类的特性,就无法用继承完成

4.适配器模式调用的时序图

1:调用客户端需要的功能-->1:1这个功能会由适配器来实现-->1:1:1适配器会转调被适配对象的功能

适配器的实现

1.适配器的常见实现

适配器通常是一个类,一般会让适配器去实现Target接口,然后在适配器的具体实现里面调用Adaptee.

2:智能适配器

可以在适配器的实现里面,加入新的功能实现,这种适配器被称为智能适配器

  1. 适配多个Adaptee
  2. 适配器Adapter实现的复杂程度
    适配器Adapter实现的复杂程度取决于Adaptee和Target的相似程度

5.缺省适配

为一个接口提供缺省实现:继承缺省适配器

双向适配器.png

example3

LogModel.java




package com.lhsjohn.adapter.example3;

import java.io.Serializable;

/**
 * 日志数据对象
 * @author lihuashuo
 *
 */
public class LogModel implements Serializable {

    //日志编号
    private String logId;
    //操作人员
    private String operaterUser;
    //操作时间,以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 getOperaterUser() {
        return operaterUser;
    }
    public void setOperaterUser(String operaterUser) {
        this.operaterUser = operaterUser;
    }
    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;
    }
    @Override
    public String toString() {
        return "LogModel [logId=" + logId + ", operaterUser=" + operaterUser + ", operateTime=" + operateTime
                + ", logContent=" + logContent + "]";
    }
    
    
    
    
    
}



LogFileOperateApi.java


package com.lhsjohn.adapter.example3;

import java.util.List;

/**
 * 日志文件操作接口
 * @author lihuashuo
 *
 */
public interface LogFileOperateApi {

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


LogFileOperate.java

package com.lhsjohn.adapter.example3;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
 * 实现对日志文件的操作
 * @author lihuashuo
 *
 */
public class LogFileOperate implements LogFileOperateApi {

    //日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log
    private String logFilePathName="AdapterLog.log";
    
    /**
     * 构造方法,传入文件的路径和名称
     * @param logFilePathName 文件的路径和名称
     */
    public LogFileOperate(String logFilePathName) {
        //先判断是否传入了文件的路径和名称
        //如果是的话,就重新设置操作的日志文件的路径和名称
        if(logFilePathName!=null&& logFilePathName.trim().length()>0) {         
            this.logFilePathName = logFilePathName;
        }
    }

    @Override
    public List readLogFile() {
       List list=null;
       ObjectInputStream oin=null;
       
       try {
       File f=new File(logFilePathName);
       if(f.exists()) {
            oin=new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
            list=(List) oin.readObject();     
           }
       
       }catch (Exception e) {
           e.printStackTrace();
        }finally {
            try {
                if(oin!=null) {
                    oin.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
       
        return list;
    }

    @Override
    public void writeLogFile(List list) {
       File f=new File(logFilePathName);
       ObjectOutputStream oout=null;
       try {
        oout=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
        oout.writeObject(list);
       } catch (Exception e) {
          e.printStackTrace();
      }finally {
          try {
             if(oout!=null) {
                 oout.close();
                }
              } catch (Exception e) {
                e.printStackTrace();
            }
          }
      }

    }



LogDbOperaterApi.java

package com.lhsjohn.adapter.example3;

import java.util.List;

/**
 * 定义操作日志的应用接口
 * 简单定义了增删改查的方法
 * @author lihuashuo
 *
 */
public interface LogDbOperaterApi {
   /**
    * 新增日志
    * @param lm 需要新增的日志对象
    */
    public void createLog(LogModel lm);
    
    /**
     * 删除日志
     * @param lm 需要修改的日志对象
     */
    public void updateLog(LogModel lm);
    
    
    /**
     * 删除日志
     * @param lm 需要删除的日志对象 
     */
    public void removeLog(LogModel lm) ;
    
    /**
     * 获取所有的日志
     * @return 所有的日志对象
     */
    public List getAllLog();
    
    /**
     * 删除所有的日志
     */
    public void removeAll();
    
    
    
    
}



LogDbOperater.java


package com.lhsjohn.adapter.example3;

import java.util.List;

import com.lhsjohn.adapter.example3.LogDbOperaterApi;
import com.lhsjohn.adapter.example3.LogModel;
/**
 * DB存储日志的实现,这里是模拟
 * @author lihuashuo
 *
 */
public class LogDbOperate implements LogDbOperaterApi {

    @Override
    public void createLog(LogModel lm) {
        System.out.println("now in LogDbOperate createLog,lm="+lm);

    }

    @Override
    public void updateLog(LogModel lm) {
        System.out.println("now in LogDbOperate updateLog,lm="+lm);

    }

    @Override
    public void removeLog(LogModel lm) {
        System.out.println("now in LogDbOperate removeLog,lm="+lm);

    }

    @Override
    public List getAllLog() {
    System.out.println("now in LogDbOperate getAllLog");
    return null;
    }

    @Override
    public void removeAll() {
        System.out.println("now in LogDbOperate removeAll");
    }

}



TwoDirectAdapter


package com.lhsjohn.adapter.example3;

import java.util.List;

import com.lhsjohn.adapter.example3.LogModel;

/**
 * 双向适配器对象
 * 
 * @author lihuashuo
 *
 */
public class TwoDirectAdapter implements LogDbOperaterApi, LogFileOperateApi {

    /**
     * 持有需要被适配的文件存储日志的对象
     */
    private LogFileOperateApi fileLog;
    /**
     * 持有需要被适配的DB存储日志的接口对象
     */
    private LogDbOperaterApi dbLog;

    /**
     * 构造方法,传入需要被适配的对象
     * 
     * @param fileLog
     *            需要被适配的文件存储日志的对象
     * @param dbLog
     *            需要被是配的DB存储日志的对象
     */
    public TwoDirectAdapter(LogFileOperateApi fileLog, LogDbOperaterApi dbLog) {
        this.fileLog = fileLog;
        this.dbLog = dbLog;
    }

    /*---------以下是把稳健操作的方式适配成DB实现方式的接口-------------*/

    @Override
    public void createLog(LogModel lm) {
        // 1:先读取文件的内容
        List list = fileLog.readLogFile();
        // 2:加入新的日志对象
        list.add(lm);
        // 3.重新写入文件
        fileLog.writeLogFile(list);
    }

    @Override
    public void updateLog(LogModel lm) {
        // 1:先读取文件的内容
        List 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;
            }
        }
    }

    @Override
    public void removeLog(LogModel lm) {
        // 1:先读取文件的内容
        List list = fileLog.readLogFile();
        // 2:删除对应的日志对象
        list.remove(lm);
        // 3:重新写入文件
        fileLog.writeLogFile(list);

    }

    @Override
    public List getAllLog() {
        return fileLog.readLogFile();
    }
    
    /* ----以下是把DB操作的方式适配成为文件实现的方式的接口*/

    @Override
    public List readLogFile() {
        return dbLog.getAllLog();
    }

    @Override
    public void writeLogFile(List list) {
        //1:最简单的实现思路,先删除数据库中的数据
        dbLog.removeLog(null);
        //2:然后循环把现在的数据加入到数据库中
        for (LogModel lm : list) {
            dbLog.createLog(lm);
        }

    }

    @Override
    public void removeAll() {
        System.err.println("now in two driect remove All");
    }

}




Client.java


package com.lhsjohn.adapter.example3;

import java.util.ArrayList;
import java.util.List;

public class Client {
   
    public static void main(String[] args) {
        //准备日志内容,也就是测试的数据
        LogModel lm1=new LogModel();
        lm1.setLogId("001");
        lm1.setOperaterUser("lhsjohn");
        lm1.setOperateTime("2018-08-24 00:30:21");
        lm1.setLogContent("这是一个测试");
        
        List list=new ArrayList();
        list.add(lm1);
        
        //创建操作日志文件的对象
        LogFileOperateApi fileLogApi=new LogFileOperate("");
        LogDbOperaterApi dbLogApi=new LogDbOperate();
        
        //创建经过双向适配后的操作接口日志的接口对象
        LogFileOperateApi fileLogApi2=new TwoDirectAdapter(fileLogApi, dbLogApi);
        LogDbOperaterApi dbLogApi2=new TwoDirectAdapter(fileLogApi, dbLogApi);
        
       
        //先测试从文件操作适配器第二版,虽然调用第二版的接口,实际上是文件操作在实现
        dbLogApi2.createLog(lm1);
        List allLog = dbLogApi2.getAllLog();
        System.out.println("allLog="+allLog);
       
        //再测试从数据库存储适配成第一版的接口,调用第一版的接口,实际上是数据库相关在实现
        System.out.println("------------------------File Api");
        fileLogApi2.writeLogFile(list);
        fileLogApi2.readLogFile();
        
        
        
    }
}





控制台输出

allLog=[LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试], LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试]]
------------------------File Api
now in LogDbOperate removeLog,lm=null
now in LogDbOperate createLog,lm=LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试]
now in LogDbOperate getAllLog



然后再深入理解一下适配器模式

对象适配器和类适配器

1:对象适配器的实现:依赖于对象组合,前面示例都是对象适配器的实现方式

2:类适配器的实现:采用多重继承对一个接口与另一个接口匹配。由于java不支持多重继承,所以到目前没涉及到。

但是java可以类似去实现类适配器,采用继承加实现接口的方式

3.png

最后来一个总结吧

适配器模式的本质

转换匹配,复用功能

何时使用适配器模式

1:如果你想使用一个已经存在的类,但它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。

2:如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况下可以使用适配器模式,到时候需要什么就适配什么

3:如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况下就可以选用对象适配器,直接适配这些子类的父类就可以了。

作者:lhsjohn

你可能感兴趣的:(设计模式思考之适配器模式)