适配器模式
适配器模式定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。——《设计模式:可复用面向对象软件的基础》
下图中是一个生活中的例子,标准的AC插头没办法直接使用欧式电源插座,通过交流电适配器,插头和插座之间就可以正常工作了。
再比如,生活中给手机充电,中国家用电的电压为220V,如果给手机充电直接使用220V的电压,手机就该爆炸了,所以充电时使用的充电器就承担了适配器的角色,将220V的电压转换成5V电压,使手机能正常充电。
适配器模式中主要有三种角色,适配器将被适配者中的方法适配为目标接口中的方法:
- 目标:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
- 适配者:已有接口,但是和客户器期待的接口不兼容
- 适配器:将已有接口转换成目标接口
类适配器模式
适配器实现目标接口,继承适配者,适配器重写目标接口中的方法,在该方法内部可以调用所继承的适配者的方法来实现适配
举例:将220V电压适配成5V
适配者:已经存在的类,但能被新的需求直接使用,需要进行一定的转换
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "V");
return src;
}
}
目标接口:规定需要的接口类型
public interface IVoltage5V {
int output5V();
}
适配器:实现目标接口,继承适配者
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = output220V();
int dstV = srcV/44;
System.out.println("电压=" + dstV + "V");
return dstV;
}
}
测试:给手机充电
public class Phone {
// 充电
public void charging(IVoltage5V iVoltage5V) {
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5V可以充电");
} else {
System.out.println("电压为不是5V,不可以充电");
}
}
}
类适配器模式注意事项和细节
- Java是单继承机制,所以类适配器需要继承适配者类这一点算是一个缺点,因为这要求目标接口是一个接口,有一定局限性。
- 适配者类的方法在适配器中都会暴露出来,也增加了使用成本。
- 由于继承了适配者类,所以它可以根据需要重写适配者类的方法,使得适配器的灵活性增强了。
对象适配器模式
- 基本思路和类适配器模式相同,只是将适配器类做修改,不是继承适配者,而是持有适配者类的实例,以解决兼容性问题。即:聚合适配者,实现目标接口。
- 根据 “合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种。
举例:将220V电压适配成5V(只是在适配器类处有所不同)
适配器:实现目标接口,组合/聚合适配者,这里采用聚合和组合的方式都可以,根据自己实际需要选择
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V = new Voltage220V();
@Override
public int output5V() {
int srcV = output220V();
int dstV = srcV/44;
System.out.println("电压=" + dstV + "V");
return dstV;
}
}
对象适配器模式的注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成服用原则,只用组合代替继承,所以它解决了类适配器必须继承适配者的局限性问题,也不再要求目标一定是接口。
- 使用成本低,更灵活。
缺省适配器模式(接口适配器模式)
当一个接口中有多个方法,但调用者并不想实现该接口的所以方法时,可以使用缺省适配器模式。
- 原理是,定义一个抽象类来默认实现接口中的所有方法,即:实现接口,重写接口中的所有方法,但不做具体实现
- 调用者通过继承抽象类来重写需要的方法
举例:缺省适配器
目标接口:目标接口中有多个方法,但我不想实现接口中的所有方法,只想实现其中一个或两个
public interface IVoltage5V {
void m1();
void m2();
void m3();
void m4();
}
适配器:是一个抽象类,不能被实例化,重写了接口的所有方法,但不做具体实现
public abstract class AbstractVoltage5V implements IVoltage5V {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
测试:匿名内部类方式
public class Main {
public static void main(String[] args) {
IVoltage5V m1 = new AbstractVoltage5V() {
@Override
public void m1() {
System.out.println("m1");
}
};
m1.m1();
}
}
hint:这里不能使用Lambda
表达式,Lambda
转换的目标类型必须是接口
适配器模式的源码级应用
在SpringMVC中的应用
DispatcherServlet的doDispatch方法是SpringMVC工作的主要方法,下面仅保留了MVC流程中关键的代码,并对此进行了注释说明
/**
* 1、用户请求
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
/**
* 2-3、进入这个方法,遍历List<HandlerMapping>调用 HandlerMapping.getHandler(request) 返回 HandlerExecutionChain
* 返回 HandlerExecutionChain 中包含Object类型的 handler对象(处理器对象),注意这里的处理器对象的类型是Object类型
*/
mappedHandler = getHandler(processedRequest);
/**
* 4、进入这个方法,遍历 List<HandlerAdapter>,这里包含了预先加载的处理器适配器(处理器和处理器适配器是一一对应的,如HttpRequestHandler和HttpRequestHandlerAdapter)
* HandlerAdapter接口定义了supports方法用于判断当前处理器适配器于传入的处理器对象的关系
* 找到后返回当前处理器对象的处理器适配器
*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/**
* 执行拦截器链List<HandlerInterceptor>的preHandle前置处理
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
/**
* 5-7、通过处理器适配器的handle方法调用处理器对象的handleRequest方法,
* 【疑问:适配器的作用在哪,为什么不直接调用处理器对象的handleRequest方法,反而加了一层处理器适配器,
* 需要注意处理器对象的类型是Object类型,可以通过if-else来判断处理器对象的类型,但如果新增了一种处理器类,
* 则需要在代码中加入if语句进行判断,这显然违反了开闭原则。
* 而增加一层适配器就很好的解决了这个问题,新增一种处理器的同时也添加一个对应的处理器适配器,并存入List<HandlerMapping>集合中,
* 在步骤4中即可得到对应的处理器适配】
*/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
/**
* 为ModelAndView设置默认名
*/
applyDefaultViewName(processedRequest, mv);
/**
* 执行拦截器链List<HandlerInterceptor>的postHandle后置处理
*/
mappedHandler.applyPostHandle(processedRequest, response, mv);
/**
* 8-11、在该方法对ModelAndView进行视图解析,并调用render(mv, request, response);方法完成渲染视图并返回
*/
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
确定当前请求的处理程序
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
确定当前请求的处理程序适配器
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");
}
查看HttpRequestHandlerAdapter适配器源代码
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
可以看出,适配器和适配者间是一一对应的,HttpRequestHandlerAdapter
中已经默认指定了它的适配者是HttpRequestHandler
。
总结
这里使用到的适配器模式中,HandlerAdapter
接口担任目标,HadnlerAdapter
的实现类担任适配器,各种处理器担任适配者。
这里适配器模式的使用不是很明显,先看一下适配器模式的定义:适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
分析:
- 原本的处理器对象是Object类型,可以通过if-else来转换类型,但违背开闭原则,不符合客户预期的接口。
- 为满足客户需求,定义了一个
HandlerAdapter
接口,里面的方法是客户期待的方法。 - 创建具体处理器的适配器,实现
HandlerAdapter
接口,在方法参数中传入适配者(处理器对象)
在线程中的应用
创建线程的方式有三种:
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,重写call方法
java中是单继承,所以继承Thread会有一定的局限性,一般使用后两种。
FutureTask类有两中构造器:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
可以看到,FutureTask(Runnable runnable, V result)
构造器内部调用Executors.callable
方法,在该方法的内部返回了一个new RunnableAdapter<T>(task, result)
,RunnableAdapter
就是一个适配器,将Runnable
适配为Callable
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
RunnableAdapter
是Executors
的一个静态内部类,其实现了Callable
接口内部聚合了Runnable
,将传入Runnable
对象适配为Callable
,这是典型的对象适配器模式