现实生活中的例子
1、不同国家的插座是有区别的,如果我们去国外旅游,需要带上国外的插头转换器,来能兼容国外的插座;
2、手机的耳机孔有圆头和扁头,如果扁头的耳机孔想接圆头的耳机就需要一个耳机的转换器;
上述所说的转换器,其实就是适配器;它是用来做兼容的;
基本概念
- 适配器模式(Adapter Pattern):将某个类的接口转换成客户端期望的另一个接口,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper);
- 适配器模式属于结构型模式;
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式;
适配器的3个角色
-
目标 (target):最终使用的对象;
-
适配者 (adaptee):做兼容处理的对象;
-
被适配者 (adapter):需要被兼容的对象;
工作原理
-
适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;
-
从用户的角度看不到被适配者;
-
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;
3种适配器模式
- 类适配器模式
- 对象适配器模式
- 接口适配器模式
1、类适配器模式
以生活中充电器为例,充电器本身相当于适配者 (Adapter),220V 交流电相当于被适配者 (adapter),我们的目标(target) 想把220V交流电转成5V直流电;
代码实现:
- Voltage220V,被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
- IVoltage5V,适配接口
public interface IVoltage5V {
public int output5V();
}
- VoltageAdapter,适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
- Phone类,需要用到适配器进行兼容
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 不能充电~~");
}
}
}
- Client类,测试类
public class Client {
public static void main(String[] args) {
System.out.println(" === 类适配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
2、对象适配器模式
还是以上面的例子为例,这一次VoltageAdapter适配器类不再继承Voltage220V,而是以聚合的方式来代替继承,符合设计模式中的"合成复用原则";java是单继承机制,这样可以保留对象继承权;
代码实现:
- VoltageAdapter,适配器类
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V; // 组合
//通过构造器,传入一个 Voltage220V 实例
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if(null != voltage220V) {
int src = voltage220V.output220V();//获取220V 电压
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
- Client类,测试类
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(" === 对象适配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
其他的类不需要修改,省略…
优点:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承被适配者的局限性问题;
3、接口适配器模式
-
接口适配器模式(Default Adapter Pattern),也叫缺省适配器模式;
-
核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;
-
适用于一个接口不想使用其所有的方法的情况;
代码实现:
- 定义一个接口
public interface InterfaceTest {
public void m1();
public void m2();
public void m3();
public void m4();
}
- 抽象类 AbsAdapter 将 InterfaceTest 的方法进行默认实现
public abstract class AbsAdapter implements InterfaceTest {
//默认实现
public void m1() {}
public void m2() {}
public void m3() {}
public void m4() {}
}
- Client 调用接口,重写接口方法
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们 需要使用 接口方法
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
适配器模式在 SpringMVC 中的应用
springMVC处理请求流程图
- 第1步 用户发送请求至前端控制器DispatcherServlet;
- 第2,3步 DispatcherServlet收到请求,根据请求url调用HandlerMapping处理器映射器找到具体的处理器;生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
- 第4步 DispatcherServlet通过HandlerAdapter处理器适配器调用具体的处理器;(这一步用到适配者模式)
- 第5,6步 执行处理器(Controller,也叫后端控制器),返回ModelAndView;
- 第7步 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- 第8步 DispatcherServlet将ModelAndView传给ViewReslover视图解析器(但是如果加上@responsebody注解,则返回值不通过viewResolver,而是直接返回object);
- 第9步 ViewReslover解析后返回具体View;
- 第10步 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中);
- 第11步 DispatcherServlet响应用户。
SpringMVM 中的 HandlerAdapter(上图的第4步), 就使用了适配器模式;
SpringMVC源码:
DispatcherServlet类下有一个获得处理器适配器的方法:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//遍历所有的处理器适配器,找到支持处理当前handler的处理器适配器(HandlerAdapter是一个接口,其实返回的是一个实现HandlerAdapter的子类)
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");
}
HandlerAdapter接口:
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
实现HandlerAdapter子类有这些,使得每一种controller有对应的适配器实现类,每种controller有不同的实现方式;
使用 HandlerAdapter 的原因分析:
如果处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果直接调用 Controller 方法,就得不断使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则;
说明:
- Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类;
- 适配器代替 controller执行相应的方法;
- 扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展;
适配者模式在线程中的应用【2022-02-17补充】
我们知道开启线程有3种方式;
-
继承Thread类,重写run方法;
-
实现Runnable 接口,重写run方法;
-
实现Callable接口,重写call方法;
最常用的是Runnable
和Callable
方法(因为对象是单继承多实现的,继承Thread
意味着不能继承其他类,所以Thread 比较少用);而FutureTask
对象的有参构造可以同时接收Runnable和Callable参数并执行线程任务;
FutureTask对象中的Runnable和Callable相互转换就用到了适配者模式;
public class ThreadTest {
// 线程池,线程任务提交给线程池执行
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
// 线程池运行Runnable方式
FutureTask futureTask01 = new FutureTask(new Runnable () {
@Override
@SneakyThrows
public void run() {
System.out.println("线程池运行Runnable方式");
Thread.sleep(3000);
}
},String.class);
executorService.submit(futureTask01);
// 线程池运行Callable方式
FutureTask futureTask02 = new FutureTask(new Callable<String> () {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("线程池运行Callable方式");
// 返回一句话
return "线程池运行Callable方式返回:" + Thread.currentThread().getName();
}
});
executorService.submit(futureTask02);
System.out.println(futureTask02.get());
}
}
接下来我们看FutureTask
类的两个构造器;
// 使用 Callable 进行初始化
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
// 使用 Runnable 初始化,并传入 result 作为返回结果。
// Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
// Executors.callable方法
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
//RunnableAdapter类
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
组合了Runnable
接口,并提供一个入参为Runnable
的构造器,构造器返回的是RunnableAdapter
对象,而RunnableAdapter
实现了 Callable
接口,就可以直接返回Callable
对象;
所以RunnableAdapter
类就是把runnable
适配成callable
的;
看到这里相信你已经理解了~
这是非常典型的适配模型,想要把 Runnable 适配成 Callable,首先要实现 Callable 的接口,接着在 Callable 的 call 方法里面调用被适配对象(Runnable)的方法。
当然也可以将Callable适配成Runnable类,那为什么不呢?因为Callable可以提供更多的方法,例如获取返回值;
所以线程池在执行任务时可以直接接收FutureTask
对象,而不用针对 Runnable 和 Callable 两种情况提供不同的实现思路;
总结
- 三种命名方式,是根据被适配者是以怎样的形式给到适配者(在 Adapter 里的形式)来命名的;
- 3种适配器模式
- 以类给到适配器,在适配者里,就是将被适配者当做类,他们是继承关系;
- 对象适配器:以对象给到适配器,在适配者里,将被适配者作为一个对象聚合,他们是关联关系;
- 接口适配器:以接口给到适配器,在适配者里,将被适配者作为一个接口实现,他们是实现关系;
- 适配器模式模式最大的作用是将原本不兼容的接口融合在一起工作;