美文网首页
设计模式思考之适配器模式

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

作者: lhsjohn | 来源:发表于2019-03-03 22:54 被阅读0次

    适配器模式的定义

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

    适配器模式.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<LogModel> readLogFile();
        /**
         * 写日志文件,把日志列表写到日志文件中去
         * @param list 要写到日志文件的日志列表
         */
        public void writeLogFile(List<LogModel> 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<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 (Exception e2) {
                    e2.printStackTrace();
                }
            }
           
            return list;
        }
    
        @Override
        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 (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<LogModel> 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<LogModel> list = adaptee.readLogFile();
            //2:加入新的日志对象
            list.add(lm);
            //3.重新写入文件
            adaptee.writeLogFile(list);
        }
    
        @Override
        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;
                }
            }
    
        }
    
        @Override
        public void removeLog(LogModel lm) {
            //1:先读取文件的内容
            List<LogModel> list = adaptee.readLogFile();
            //2:删除对应的日志对象
            list.remove(lm);
            //3:重新写入文件
            adaptee.writeLogFile(list);
    
        }
    
        @Override
        public List<LogModel> 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<LogModel> list=new ArrayList<LogModel>();
            list.add(lm1);
            
            //创建操作日志文件的对象
            LogFileOperateApi logFileApi=new LogFileOperate("");
            
            //创建新版的操作日志的接口对象
            LogDbOperaterApi api=new Adapter(logFileApi);
            
            //保存日志文件
            api.createLog(lm1);
            //未使用适配器之前的:api.writeLogFile(list);
            
            //读取日志文件的内容
            //未使用适配器的:List<LogModel> list2 = api.readLogFile();
            //使用适配器的
            List<LogModel> 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<LogModel> readLogFile();
        /**
         * 写日志文件,把日志列表写到日志文件中去
         * @param list 要写到日志文件的日志列表
         */
        public void writeLogFile(List<LogModel> 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<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 (Exception e2) {
                    e2.printStackTrace();
                }
            }
           
            return list;
        }
    
        @Override
        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 (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<LogModel> 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<LogModel> 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<LogModel> list = fileLog.readLogFile();
            // 2:加入新的日志对象
            list.add(lm);
            // 3.重新写入文件
            fileLog.writeLogFile(list);
        }
    
        @Override
        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;
                }
            }
        }
    
        @Override
        public void removeLog(LogModel lm) {
            // 1:先读取文件的内容
            List<LogModel> list = fileLog.readLogFile();
            // 2:删除对应的日志对象
            list.remove(lm);
            // 3:重新写入文件
            fileLog.writeLogFile(list);
    
        }
    
        @Override
        public List<LogModel> getAllLog() {
            return fileLog.readLogFile();
        }
        
        /* ----以下是把DB操作的方式适配成为文件实现的方式的接口*/
    
        @Override
        public List<LogModel> readLogFile() {
            return dbLog.getAllLog();
        }
    
        @Override
        public void writeLogFile(List<LogModel> 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<LogModel> list=new ArrayList<LogModel>();
            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<LogModel> 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

    相关文章

      网友评论

          本文标题:设计模式思考之适配器模式

          本文链接:https://www.haomeiwen.com/subject/ekrvuqtx.html