1.定义

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作。

2.解决问题

——日志管理

不用模式的解决方案

版本一:用户要求日志以文件的形式记录

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

版本二:用户要求日志以数据库的形式记录

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();
}

使用适配器模式来解决问题

public class Adapter implements LogDbOperateApi {
    private LogFileOperateApi adaptee;
    public Adapter(LogFileOperateApi adaptee) {
        super();
        this.adaptee = adaptee;
    }
    public void createLog(LogModel lm) {
        //读取文件内容
        List<LogModel> list = adaptee.readLogFile();
        //加入新日志
        list.add(lm);
        //重新写入文件
        adaptee.writeLogFile(list);
    }
    public void updateLog(LogModel lm) {
        //读取文件内容
        List<LogModel> list = adaptee.readLogFile();
        //修改相应日志
        //重新写入文件
        adaptee.writeLogFile(list);
    }
    public void removeLog(LogModel lm) {
        //读取文件内容
        List<LogModel> list = adaptee.readLogFile();
        //删除相应日志
        list.remove(lm);
        //重新写入文件
        adaptee.writeLogFile(list);
    }
    public List<LogModel> getAllLog() {
        return adaptee.readLogFile();
    }
}
public class Client {
    public static void main(String[] args) {
        LogModel lml = new LogModel();
        // 创建操作日志文件的对象
        LogFileOperateApi logFileApi = new LogFileOperate("");
        // 创建新版操作日志的接口对象
        LogDbOperateApi api = new Adapter(logFileApi);
        // 保存日志文件
        api.createLog(lml);
    }
}

3.模式讲解

在第一版中采用的文件存储方式,它的操作方式和第二版不一样,这导致现在的客户端无法以同样的方式来直接使用第一版的实现。要想同时支持文件和数据库存储方式,需要额外地做一些工作。

一种很容易想到的方式是直接修改已有的第一版的代码,这种方式不太好,因为可能会导致其他依赖于这些实现的应用不能正常运行。

解决思路

上面的问题,根源在与接口不兼容,功能都是实现了的,即只要让两个接口匹配起来。按照适配器的方式,可以顶一个类来实现第二版的接口,然后在内部实现的时候,转调第一版已经实现了的功能,这样就可以通过对象组合的方式,既服用了第一版已有的功能,同时又在接口上满足了第二版调用的要求。

示例代码

public interface Target {
    /**
     * 客户端请求处理的方法
     */
    public void request();
}
public class Adaptee {
    /**
     * 原本已经存在,已经实现的方法
     */
    public void specificRequest() {
        // 具体功能处理
    }
}
public class Adapter implements Target {
    // 持有需要被是配的接口对象
    private Adaptee adaptee;
                
    /**
     * 构造函数,传入需要被适配的对象
     * @param adaptee 需要被是配的独享
     */
    public Adapter(Adaptee adaptee) {
        super();
        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();
    }
}

应用范围

适配器模式的主要功能是进行转换匹配,目的是复用已有的功能,而不是来实现新的接口。即客户端需要的功能应该是已实现好的,不需要适配器模式来实现,适配器模式主要负责把不兼容的接口转换成客户端期望的样子。

Adaptee和Target的关系

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

适配器模式调用顺序示意图

151402872.png

适配器模式的优缺点:

优点:更好的复用性,更好地可扩展性

缺点:过多的使用适配器,会让系统非常凌乱,不容易整体进行把握

4.思考

适配器模式的本质是:转换匹配,复用功能

适配器通过转换调用已有的实现,从而把已有的实现匹配成为需要的接口,使之能满足客户端的需求,即转换匹配是手段,复用功能才是目的。

何时选用适配器模式

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

如果想:创建一个可以服用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式。



说明:笔记内容摘自《研磨设计模式》陈臣,王斌

关联:整理了一些Java软件工程师的基础知识点