XiaoLin's Blog

Xiao Lin

适配器设计模式学习笔记

27
2024-02-26

适配器模式介绍

适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要目的是将一个类的接口转换成客户期望的另一个接口。适配器模式使得原本接口不兼容的类可以协同工作。

适配器模式包含以下角色:

  1. Target(目标抽象类):定义了客户需要使用的接口。
  2. Adapter(适配器类):将 Adaptee 的接口进行适配转换。
  3. Adaptee(适配者类):需要被转换接口的对象。
  4. Client(客户类):通过适配器接口 Target 去使用 Adaptee 的功能。

适配器模式有三种常见的应用方式:

  1. 对象适配器:通过对象的组合来达到适配目的。
  2. 类适配器:通过类的继承或者接口的实现来达到适配目的。

适配器模式适用于以下场景:

  1. 当需要在不修改现有代码结构的情况下实现新功能时。
  2. 当现有系统中的类和接口不易修改时。

需要注意的是,过度使用适配器模式可能导致代码可读性变差,不易维护。在实际开发过程中,应根据具体需求选择合适的设计模式。

适配器模式的实现

适配器模式有两种实现方式:类适配器和对象适配器。

  1. 类适配器模式:通过类的继承来实现适配器模式。在这种实现方式中,适配器类继承自目标接口,同时实现目标接口和源接口。通过在适配器类中调用源类的方法,将源类的接口转换为目标接口。
  2. 对象适配器模式:将方法的实现委托给其他人来执行。在这种实现方式中,适配器类包含一个源角色(被适配者)的实例,通过调用源角色的方法来实现目标接口的方法。适配器类实现了目标接口,但不继承自任何类。

这两种实现方式可以根据具体场景和需求进行选择。类适配器模式适用于适配器类与源类有共同的父类或接口的情况,而对象适配器模式则适用于适配器类与源类没有直接关系的情况。

我们定义适配器模式的适配者和目标抽象角色

package xyz.xiaolinz.demo.adapter.classadapter;
/**
 * 类适配器模式 - 需要适配的类
 *
 * @author huangmuhong
 * @date 2023/08/10
 * @version 1.0.0
 */
public class Adapter {
  public void specificRequest() {
    System.out.println("特殊请求");
  }
}


package xyz.xiaolinz.demo.adapter.classadapter;



/**
 * 类适配器模式 - 目标角色 定义了目标角色的行为,这里是request()方法
 *
 * @author huangmuhong
 * @date 2023/08/10
 * @version 1.0.0
 */
public interface Target {

  /**
   * 要求
   *
   * @author huangmuhong
   * @date 2023/08/10
   * @since 1.0.0
   */
  void request();
}



类适配器模式

类适配器模式就是继承目标类实现目标接口从而达到适配的功能

package xyz.xiaolinz.demo.adapter.classadapter;
/**
 * 类适配器模式 - 适配器角色
 * <p>
 *   适配器角色是适配器模式的核心,它是适配器模式的重点。
 *   适配器模式的实现方式有两种:类适配器和对象适配器。
 *   类适配器采用继承的方式,对象适配器采用组合的方式。
 *   适配器角色实现了目标角色的接口,同时继承了需要适配的类。
 *   适配器角色的request()方法中调用了需要适配的类的specificRequest()方法。
 *
 *   适配器模式的优点:
 *   1. 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器角色能够搞定他们就成。
 *   2. 增加了类的透明性,客户端只需要访问目标角色,对于如何适配目标角色的具体行为则透明。
 *   3. 提高了类的复用度,适配器角色可以重复使用,而不是每次都重新写适配器。
 *   4. 灵活性好,适配器角色不仅可以适配类,还可以适配对象。
 * </p>
 * 
 * @author huangmuhong
 * @date 2023/08/10
 * @version 1.0.0
 */
public class ClassAdapter extends Adapter implements Target {

  @Override
  public void request() {
    // 这里是对需要适配的类的方法进行调用
    specificRequest();
  }
}

对象组合适配模式

对象组合方式与类模式的不同之处就在于将继承的方式改为了组合,降低了代码的耦合程度

package xyz.xiaolinz.demo.adapter.objectadapter;

/**
 * 类适配器模式 - 适配器角色
 *
 * <p>适配器角色是适配器模式的核心,它是适配器模式的重点。 适配器模式的实现方式有两种:类适配器和对象适配器。 对象适配器模式是适配器模式常见的实现方式,通过组合的方式实现。
 *
 * <p>适配器模式的优点: 1. 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器角色能够搞定他们就成。 2.
 * 增加了类的透明性,客户端只需要访问目标角色,对于如何适配目标角色的具体行为则透明。 3. 提高了类的复用度,适配器角色可以重复使用,而不是每次都重新写适配器。 4.
 * 灵活性好,适配器角色不仅可以适配类,还可以适配对象。
 *
 * @author huangmuhong
 * @date 2023/08/10
 * @version 1.0.0
 */
public class ObjectAdapter implements Target {

  protected Adapter adapter;

  public ObjectAdapter(Adapter adapter) {
    this.adapter = adapter;
  }

  @Override
  public void request() {
    // 这里是对需要适配的类的方法进行调用
    adapter.specificRequest();
  }
}

源码中的使用

log4j 中的应用

Java 中有很多日志框架,比较常用的有 log 4 j、logback,以及 JDK 提供的 JUL (java. util. logging) 和 Apache 的 JCL(Jakarta Commons Logging))等。

大部分日志框架都提供了相似的功能,比如按照不同级别(debug、info、warn、erro……)打印日志等,但它们却并没有实现统一的接口。这主要是历史的原因,它不像 JDBC 那样,一开始就制定了数据库操作的接口规范。

如果我们只是开发一个自己用的项目,那用什么日志框架都可以,log 4 j、logback 随便选一个就好。但是,如果我们开发的是一个集成到其他系统的组件、框架、类库等,那日志框架的选择就没那么随意了。

我们举一个例子,项目中用到的某个组件使用 log 4 j 来打印日志,而我们项目本身使用的是 logback。将组件引入到项目之后,我们的项目就相当于有了两套日志打印框架。每种日志框架都有自己特有的配置方式。所以,我们要针对每种日志框架编写不同的配置文件(比如,日志存储的文件地址、打印日志的格式)。如果引入多个组件,每个组件使用的日志框架都不一样,那日志本身的管理工作就变得非常复杂。所以,为了解决这个问题,我们需要统一日志打印框架。

Slf 4 j 这个日志框架你肯定不陌生,它相当于 JDBC 规范,是一套门面日志,提供了一套打印日志的统一接口规范。不过,它只定义了接口,并没有提供具体的实现,需要配合其他日志框架(log 4 j、logback……)来使用。

不仅如此,Slf4j 的出现晚于 JUL、JCL、log 4 j 等日志框架,所以,这些日志框架也不可能牺牲掉版本兼容性,将接口改造成符合 Slf4j 接口规范。Slf4j 也事先考虑到了这个问题,所以,它不仅仅提供了统一的接口定义,还提供了针对不同日志框架的适配器。对不同日志框架的接口进行二次封装,适配成统一的 Slf 4 j 接口定义。具体的代码示例如下所示:

我们接下来就以 slf4j 为例,看看其中的绑定和桥接功能是如何巧妙实现兼容不同形式的日志的。

slf4j-log4j12 是一个 SLF4J 的实现库,它将 SLF4J API 的日志记录请求转发给 Log4j 1.2 作为底层日志框架。它实际上是一个适配器,将 SLF4J API 与 Log4j 1.2 API 进行了适配。我们来看一下源码中的关键部分,以理解其实现原理。

(1)Log4jLoggerFactory:slf4j-log4j12 实现了 SLF4J 的 ILoggerFactory 接口,创建 Log4j 1.2 的 Logger 实例。这个工厂类负责将 SLF4J 的请求转换为 Log4j 1.2的请求。

public class Log4jLoggerFactory implements ILoggerFactory {

  public Logger getLogger(String name) {
    // 获取Log4j 1.2的Logger实例
    org.apache.log4j.Logger log4jLogger = LogManager.getLogger(name);
    // 将Log4j 1.2的Logger实例包装成SLF4J的Logger实例并返回
    return new Log4jLoggerAdapter(log4jLogger);
  }
}

(2)Log4jLoggerAdapter:这个类组合了 SLF4J 的 Logger 接口,将 SLF4J API 转换为 Log4j 1.2 的 API。它包装了一个 Log4j 1.2 的 Logger 实例,用于实际的日志记录。

public final class Log4jLoggerAdapter extends MarkerIgnoringBase {
    final Logger logger; // Log4j 1.2的Logger实例

    public Log4jLoggerAdapter(Logger logger) {
        this.logger = logger;
        this.name = logger.getName();
    }

    public boolean isDebugEnabled() {
        return logger.isDebugEnabled();
    }

    public void debug(String msg) {
        logger.log(FQCN, Level.DEBUG, msg, null);
    }

    // 其他方法,例如info(), error()等,也类似地转发给Log4j 1.2的Logger实例
}

当我们在项目中调用 SLF4J 的 LoggerFactory 获取一个 Logger 实例时,SLF4J 会自动发现并使用 slf4j-log4j12 提供的 Log4jLoggerFactory。Log4jLoggerFactory 会创建一个 Log4jLoggerAdapter 实例,这个实例内部包装了一个 Log4j 1.2 的 Logger。当我们使用 SLF4J API 进行日志记录时,Log4jLoggerAdapter 会将这些请求转换为 Log4j 1.2可以处理的请求,从而实现了日志绑定。

通过这种适配器模式,slf4j-log4j12 实现了 SLF4J API 与 Log4j 1.2 的无缝集成,使得我们可以在项目中使用 SLF4J API 进行日志记录,同时底层使用 Log4j 1.2 作为实际的日志框架。这使得客户端代码只需关注 SLF4J API,而无需关心底层日志框架的实现细节。此外,这种设计还为我们提供了灵活性,可以轻松地在不同的日志框架之间进行切换,只需更改项目依赖即可。

通过使用 slf4j-log4j12,我们可以在不修改客户端代码的情况下,将项目中的日志记录从其他日志框架迁移到 Log4j 1.2,或者从 Log4j 1.2 迁移到其他框架。这大大简化了项目中日志框架迁移和升级的工作。

SpringMVC 框架

在 SpringMVC 中,为了适配各种类型的处理器(Handler),使用了适配器设计模式。例如,org. springframework. web. servlet. HandlerAdapter 接口为各种处理器提供了统一的适配。具体实现类有 org. springframework. web. servlet. mvc. method. annotation. RequestMappingHandlerAdapter 等。

// HandlerAdapter接口
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    long getLastModified(HttpServletRequest request, Object handler);
}

// RequestMappingHandlerAdapter类实现了HandlerAdapter接口
public class RequestMappingHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
    // ...
    // 判断是否支持此处理器
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }

    // 处理请求
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // ...
    }

    // 获取最后修改时间
    public long getLastModified(HttpServletRequest request, Object handler) {
        // ...
    }
}