目录
what什么是适配器模式
why为什么需要适配器模式? ? ? ?
how怎么实现适配器模式? ? ? ?
类适配器模式
对象适配器模式
类适配器和对象适配器对比
?开源框架经典案例
Dubbo之LoggerAdapter
经典的SpringMVC之HandlerAdapter
Spring中的MethodInterceptor适配器
领域驱动设计之防腐层(Anti-corruption layer)
常用场景
优缺点对比
优点
类适配器优点
对象适配器模式优点
缺点
类适配器模式的缺点
对象适配器模式的缺点
总结? ? ? ??
参考资料
what什么是适配器模式
GOF定义:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。(Gof里面说这个别名是Wrapper模式,这里和装饰器模式容易混淆,因为装饰器也可以说是Wrapper)
HeadFirst定义:将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。 ?
why为什么需要适配器模式 ? ? ? ?
?????????这个模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。者可以让客户从实现的接口解耦。如果在一段时间之后,我们想改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。
? ? ? ? 这个模式是让客户和接口绑定起来,而不说和实现绑定起来。我们可以多个适配器,每一个都负责转换不同组的后台类。
? ? ? ? 适配器模式的思想也延伸到方方面面,我们买苹果产品,欧洲和中国的插座不一样,就需要买一个转换头,这是典型的适配器场景,也是很多介绍适配器文章的经典例子。在往设计领域思考,领域驱动设计里面提到的防腐层,其实也是适配器的思想。
????????
how怎么实现适配器模式 ? ? ? ?
适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器模式 ?
?
对象适配器模式 ?
?
类适配器和对象适配器对比
这里引用HeadFirst里面的原话
? 开源框架经典案例
Dubbo之LoggerAdapter
????????日志框架何其多,每个公司甚至每个项目组估计都有一个自己的logger,为了适配不同的日志框架,dubbo中定义了自己的Logger接口,接口代码如下。同时新增了LoggerAdapter是适配不同的日志框架,该LoggerAdapter也标注了SPI注解,因此在引入dubbo後可根据项目按需配置,这是标准的不能再标准的适配器模式。
定义自己的Logger接口
package org.apache.dubbo.common.logger;
/**
* Logger interface
* <p>
* This interface is referred from commons-logging
*/
public interface Logger {
/**
* Logs a message with trace log level.
*
* @param msg log this message
*/
void trace(String msg);
/**
* Logs an error with trace log level.
*
* @param e log this cause
*/
void trace(Throwable e);
/**
* Logs an error with trace log level.
*
* @param msg log this message
* @param e log this cause
*/
void trace(String msg, Throwable e);
.... 省略其他的方法
}
日志适配器接口,具体的适配逻辑由子类实现
@SPI
public interface LoggerAdapter {
/**
* Get a logger
*
* @param key the returned logger will be named after clazz
* @return logger
*/
Logger getLogger(Class<?> key);
/**
* Get a logger
*
* @param key the returned logger will be named after key
* @return logger
*/
Logger getLogger(String key);
/**
* Get the current logging level
*
* @return current logging level
*/
Level getLevel();
/**
* Set the current logging level
*
* @param level logging level
*/
void setLevel(Level level);
/**
* Get the current logging file
*
* @return current logging file
*/
File getFile();
/**
* Set the current logging file
*
* @param file logging file
*/
void setFile(File file);
}
????????以Slf4jLoggerAdapter为例,可以看到其代码是实现了LoggerAdapter接口,并返回了一个Slf4jLogger。
public class Slf4jLoggerAdapter implements LoggerAdapter {
private Level level;
private File file;
@Override
public Logger getLogger(String key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
@Override
public Logger getLogger(Class<?> key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
@Override
public Level getLevel() {
return level;
}
@Override
public void setLevel(Level level) {
this.level = level;
}
@Override
public File getFile() {
return file;
}
@Override
public void setFile(File file) {
this.file = file;
}
}
经典的SpringMVC之HandlerAdapter
先简单看一下SpringMvc核心类DispatchServlet的处理逻辑,SpringMVC 内部是根据 HandlerMapping 将 Request 和 Controller 里面的方法对应起来的,为了方便理解,我这里把实现它的子类统称为映射处理器。
HandlerMapping 功能就是根据请求匹配到对应的 Handler ,然后将找到的 Handler 和所有匹配的 HandlerInterceptor (拦截器)绑定到创建的 HandlerExecutionChain 对象上并返回。
HandlerMapping 只是一个接口类,不同的实现类有不同的匹对方式,根据功能的不同我们需要在 SpringMVC 容器中注入不同的映射处理器 HandlerMapping 。
?HandlerAdapter 为一个接口,该接口内部定义了如下三个方法:
- supports(): 给出一个 handler,查看该 HandlerAdapter 是否支持该 handler。
- handle(): 使用本 HandlerAdapter 代理执行这个 handler 中的方法。
- getLastModified(): 查看该请求的 lastModified 值
public interface HandlerAdapter {
/**
* Given a handler instance, return whether or not this {@code HandlerAdapter}
* can support it. Typical HandlerAdapters will base the decision on the handler
* type. HandlerAdapters will usually only support one handler type each.
* <p>A typical implementation:
* <p>{@code
* return (handler instanceof MyHandler);
* }
* @param handler handler object to check
* @return whether or not this object can use the given handler
*/
boolean supports(Object handler);
/**
* Use the given handler to handle this request.
* The workflow that is required may vary widely.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler to use. This object must have previously been passed
* to the {@code supports} method of this interface, which must have
* returned {@code true}.
* @throws Exception in case of errors
* @return ModelAndView object with the name of the view and the required
* model data, or {@code null} if the request has been handled directly
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* Same contract as for HttpServlet's {@code getLastModified} method.
* Can simply return -1 if there's no support in the handler class.
* @param request current HTTP request
* @param handler handler to use
* @return the lastModified value for the given handler
* @see javax.servlet.http.HttpServlet#getLastModified
* @see org.springframework.web.servlet.mvc.LastModified#getLastModified
*/
long getLastModified(HttpServletRequest request, Object handler);
}
?????????再看HandlerAdapter的类图,这里一句话解释,不同请求方式和配置会得到不同的Adapter,另外这里adapter的匹配顺序也是有讲究的,看源码会知道是匹配到了Adapter就返回。看一下HandlerAdapter的子类。
Spring中的MethodInterceptor适配器
????????大家都知道在Aop中每个advistor 里面会有一个advice具体做切面动作,Spring提供了AspectJAfterReturningAdvice,AspectJMethodBeforeAdvice,AspectJAroundAdvice,AspectJAfterAdvice这几个advice,在XML 配置aop时候会指定<aop:after-returning/>,<aop:before/>,<aop:around/>,<aop:after/>,其实内部就是创建上面对应的这些advice。
????????AspectJAfterReturningAdvice和AspectJMethodBeforeAdvice没实现MethodInterceptor接口,其他两者则实现了该接口。而Spring Aop的方法拦截器却必须是实现了MethodInterceptor的,所以Spring提供了对应的适配器来适配这个问题,分别是MethodBeforeAdviceAdapter和AfterReturningAdviceAdapter和ThrowsAdviceAdapter。
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
private MethodBeforeAdvice advice;
/**
* Create a new MethodBeforeAdviceInterceptor for the given advice.
* @param advice the MethodBeforeAdvice to wrap
*/
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
}
领域驱动设计之防腐层(Anti-corruption layer)
????????在微服务(Microservices)架构实践中,人们大量地借用了DDD中的概念和技术,比如一个微服务应该对应DDD中的一个限界上下文(Bounded Context);在微服务设计中应该首先识别出DDD中的聚合根(Aggregate Root);还有在微服务之间集成时采用DDD中的防腐层(Anti-Corruption Layer, ACL)。
????????防腐层(Anti-corruption layer,简称 ACL)介于新应用和旧应用之间,用于确保新应用的设计不受老应用的限制。是一种在不同应用间转换的机制。
? ? ? ? 创建一个防腐层,以便根据客户自己的领域模型来爲客户提供相关功能,这个层通过另一个系统现有接口与其进行对话,而只需对那个系统做出很少的修改,甚至无需修改。在内部,这个曾在两个模型之间进行必要的双向转换。
????防腐层是将一个域映射到另一个域,这样使用第二个域的服务就不必被第一个域的概念“破坏”。?使用防腐层(Anti-corruption layer)模式可确保应用程序的设计不受限于对外部子系统的依赖。 防腐层(Anti-corruption layer)模式最先由 Eric Evans 在 Domain-Driven Design(域驱动的设计)中描述。
常用场景
- 系统需要使用现有的类,而这些类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
优缺点对比
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
类适配器优点
????????由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式优点
????????一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点
类适配器模式的缺点
????????对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
对象适配器模式的缺点
?????????与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
总结 ? ? ? ??
适配器模式是很常用的一种设计模式,?适配器不仅仅是设计模式,更是一种设计思想。?
- 适配器模式用于将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
- 在类适配器模式中,适配器类实现了目标抽象类接口并继承了适配者类,并在目标抽象类的实现方法中调用所继承的适配者类的方法;在对象适配器模式中,适配器类继承了目标抽象类并定义了一个适配者类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业务方法。
- 适配器模式的主要优点是将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”;类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类,对象适配器模式的缺点是很难置换适配者类的方法。
- 适配器模式适用情况包括:系统需要使用现有的类,而这些类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。
参考资料
领域驱动设计
适配器模式
Spring MVC学习笔记之Spring MVC组件HandlerAdapter
SpringMVC工作原理之处理映射
|