设计模式-适配器模式学习之旅

359 阅读8分钟

“这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

一、什么是适配器模式?

适配器模式(Adapter Pattern)它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作,属于结构型设计模式。

也就是说,当前系统存在两个接口A和B,客户只支持访问A接口,但是当前系统没有A接口对象,有B接口对象,客户无法识别B接口,因此需要通过一个适配器C,将B接口内容转换成A接口,从而使得客户能够从A接口获取得到B接口的内容。

在软件开发中,基本上任何问题都可以通过增加一个中间层进行解决。适配器模式其实就是一个中间层。综上,适配器模式其实起着转化/委托的作用,将一种接口转化为另一种符合需求的接口。

二、适配器模式的应用场景

提供一个转换器(适配器),将当前系统存在的一个对象转化为客户端更能够访问的接口对象。适配器使用于以下几种业务场景:

  1. 已经存在的类,它的方法和需求不匹配(方法结果相同或类似)的情况。
  2. 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案,有点亡羊补牢的感觉。

生活中的应用场景,例如电源转换头,手机转换头,显示器转换头。

三、适配器模式涉及的角色

适配器模式一般包含三种角色:

  1. 目标角色(Target):也就是我们期望的接口。
  2. 源角色(Adaptee):存在于系统中,内容满足客户需求(需转化),但接口不匹配的接口实例。
  3. 适配器(Adapter):将源角色(Adaptee)转化为目标角色(Target)的类实例。

适配器模式各角色之间的关系如下:

假设当前系统中,客户端需要访问的是Target接口,但Target接口没有一个实例符合需求,而Adaptee实例符合需求,但是客户端无法直接使用Adaptee(接口不兼容),因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee能转化为Target接口形式。

四、适配器模式的三种形式

适配器模式有3种形式:类适配器、对象适配器、接口适配器。

1. 类适配器

类适配器的原理就是通过继承来实现适配器功能,具体做法:让Adapter实现Target接口,并且继承Adaptee,这样Adapter就具备Target和Adaptee的特性,就可以将两者进行转化。

在中国民用电都是220V交流电,但我们手机使用的锂电池使用的是5V直流电。因此我们给手机充电时就需要使用电源适配器来进行转换。下面我们有代码来还原这个生活场景,创建Adaptee角色,需要被转换的对象AC220类,表示220V交流电。

public class AC220V {
    public int outputAC220V() {
        int output = 220;
        System.out.println("输出电压" + output + "V");
        return output;
    }
}

创建Target角色DC5接口,表示5V直流电的标准:

public interface DC5 {
    int outputDC5V();
}

创建Adapter适配器角色,表示把220V电流转换为5V电流。

public class PowerAdapter extends AC220V implements DC5 {
    @Override
    public int outputDC5V() {
        int outputAC220V = super.outputAC220V();
        int adapterOutput = outputAC220V / 44;
        System.out.println("使用 Adapter 输入 AC" + outputAC220V + "V,输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

客户端测试代码:

public class Test {
    public static void main(String[] args) {
        DC5 adapter = new PowerAdapter();
        adapter.outputDC5V();
    }
}

输出结果:

image.png

上面的案例中,通过增加PowerAdapter电源适配器,实现了二者的兼容。

2. 对象适配器

对象适配器的原理就是通过组合来实现适配器功能,具体做法:让Adapter实现Target接口,然后内部持有Adaptee实例,然后再Target接口规定的方法内转换Adaptee。

代码只需要更改适配器(Adapter)实现,其他与适配器一致:

public class PowerAdapter implements DC5 {

    private AC220V ac220V;

    public PowerAdapter(AC220V ac220V) {
        this.ac220V = ac220V;
    }

    @Override
    public int outputDC5V() {
        int outputAC220V = this.ac220V.outputAC220V();
        int adapterOutput = outputAC220V / 44;
        System.out.println("使用 Adapter 输入 AC" + outputAC220V + "V,输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

3. 接口适配器

接口适配器的关注点与类适配器和对象适配器的关注点不太一样,类适配器和对象适配器着重于将系统存在的一个角色(Adaptee)转化成目标接口(Target)所需内容,而接口适配器的使用场景是解决接口方法过多,如果直接实现接口,那么类会多出许多空实现的方法,类显得臃肿。此时使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰。

接口适配器的主要原理就是利用抽象类实现接口,并且空实现接口众多方法。下面我们来采用接口适配器模式实现,首先创建Target角色DC类:

public interface DC {
    int output5V();

    int output12V();

    int output24V();

    int output36V();
}

创建Adaptee角色AC220V类:

public class AC220V {
    public int outputAC220V() {
        int output = 220;
        System.out.println("输出电压" + output + "V");
        return output;
    }
}

创建Adapter角色PowerAdapter类:

public class PowerAdapter implements DC {

    private AC220V ac220V;

    public PowerAdapter(AC220V ac220V) {
        this.ac220V = ac220V;
    }

    @Override
    public int output5V() {
        int outputAC220V = this.ac220V.outputAC220V();
        int adapterOutput = outputAC220V / 44;
        System.out.println("使用 Adapter 输入 AC" + outputAC220V + "V,输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }

    @Override
    public int output12V() {
        return 0;
    }

    @Override
    public int output24V() {
        return 0;
    }

    @Override
    public int output36V() {
        return 0;
    }
}

客户端代码:

public class Test {
    public static void main(String[] args) {
        DC adapter = new PowerAdapter(new AC220V());
        adapter.output5V();
    }
}

五、适配器模式在源码中的体现

Spring中适配器模式也应用得非常广泛,例如:Spring AOP 中的AdvisorAdapter类,它有三个实现类MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter和ThrowsAdviceAdapter,先来看AdvisorAdapter的源代码:

public interface AdvisorAdapter {

  /**
   * Does this adapter understand this advice object? Is it valid to
   * invoke the {@code getInterceptors} method with an Advisor that
   * contains this advice as an argument?
   * @param advice an Advice such as a BeforeAdvice
   * @return whether this adapter understands the given advice object
   * @see #getInterceptor(org.springframework.aop.Advisor)
   * @see org.springframework.aop.BeforeAdvice
   */
  boolean supportsAdvice(Advice advice);

  /**
   * Return an AOP Alliance MethodInterceptor exposing the behavior of
   * the given advice to an interception-based AOP framework.
   * <p>Don't worry about any Pointcut contained in the Advisor;
   * the AOP framework will take care of checking the pointcut.
   * @param advisor the Advisor. The supportsAdvice() method must have
   * returned true on this object
   * @return an AOP Alliance interceptor for this Advisor. There's
   * no need to cache instances for efficiency, as the AOP framework
   * caches advice chains.
   */
  MethodInterceptor getInterceptor(Advisor advisor);

}

再看MethodBeforeAdviceAdapter类:

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

  @Override
  public boolean supportsAdvice(Advice advice) {
    return (advice instanceof MethodBeforeAdvice);
  }

  @Override
  public MethodInterceptor getInterceptor(Advisor advisor) {
    MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
    return new MethodBeforeAdviceInterceptor(advice);
  }

}

下面再来看一个Spring MVC 中的HandlerAdapter类,它也有多个子类,类图如下:

image.png

其适配器调用的关键代码还是在DispatcherServlet的doDispatch()方法中,下面我们还是来看源码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);

        // Determine handler for the current request.
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
          noHandlerFound(processedRequest, response);
          return;
        }

        // Determine handler adapter for the current request.
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // Process last-modified header, if supported by the handler.
        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
          long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }

        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }

        // Actually invoke the handler.
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        if (asyncManager.isConcurrentHandlingStarted()) {
          return;
        }

        applyDefaultViewName(processedRequest, mv);
        mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
        dispatchException = ex;
      }
      catch (Throwable err) {
        // As of 4.3, we're processing Errors thrown from handler methods as well,
        // making them available for @ExceptionHandler methods and other scenarios.
        dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
          new NestedServletException("Handler processing failed", err));
    }
    finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
        // Instead of postHandle and afterCompletion
        if (mappedHandler != null) {
          mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        }
      }
      else {
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
          cleanupMultipart(processedRequest);
        }
      }
    }
  }

在doDispatch()方法中调用了getHandlerAdapter()方法,来看代码:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
      for (HandlerAdapter adapter : this.handlerAdapters) {
        if (adapter.supports(handler)) {
          return adapter;
        }
      }
    }
    throw new ServletException("No adapter for handler [" + handler +
        "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  }

六、适配器模式的优缺点

优点:

  1. 能提高类的透明性和复用,现有的类复用但不需要改变。
  2. 目标类和适配器类解耦,提高程序的扩展性。
  3. 在很多业务场景中符合开闭原则。

缺点:

  1. 适配器模式编写过程需要全面考虑,可能会增加系统的复杂性。
  2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

七、友情链接

设计模式-工厂模式学习之旅

设计模式-单例模式学习之旅

设计模式-原型模式学习之旅

设计模式-建造者模式学习之旅

设计模式-代理模式学习之旅

设计模式-门面模式学习之旅

设计模式-装饰器模式学习之旅

设计模式-享元模式学习之旅

设计模式-组合模式学习之旅

欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。