优质博文:IT-BLOG-CN
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。在现实生活中,经常会遇到类似接口不兼容而不能在一起工作的实例,这是就需要第三者j进行适配,例如:手机与电源,当手机需要充电时,就需要充电器来适配电源。
● 适配器模式是将某个类的接口转化成客户端需要的另一个接口表示,主要的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
● 适配器模式属于结构型模式
● 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
1)、适配器模式:将一个类的接口转化成另一种接口,让原本不兼容的类可以兼容。
2)、从用户的角度看不到被适配者,是解耦的。
3)、用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。
4)、用户收到反馈结果,感觉知识和目标接口交互。如下图所示:
类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
类适配器应用实例: 以手机、充电器、电源为例来说明适配器模式。适配器模式(Adapter)包含以下主要角色:
①:目标(Target)接口:充电器输入的 5v 电压及与手机对接的头(目标)所抽象出来的接口或抽象类(当前系统业务所期待的接口,它可以是抽象类或接口)
②:适配者(Adaptee)类:220V的电源(它是被访问和适配的现存组件库中的组件)
③:适配器(Adapter)类:充电器(它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者)
【1】被适配类,即电源对象
public class Voltage220V {
//被适配类即电源,输入的值为220伏
public int output() {
int volateage = 220;
return volateage;
}
}
【2】目标接口,既将220V转化为5V的接口
public interface IVoltage5V {
//将被适配对象转化成目标对象方法
public int output5v();
}
【3】适配器,将220V转化为5V的电压的具体实现,并继承被适配器类(类适配器的原因)
public class VoltageAdpter extends Voltage220V implements IVoltage5V{
@Override
public int output5v() {
//获取目标输入的结果
int src = output();
//处理输入的电压
int dst = src/44;
return dst;
}
}
【4】客户端,手机充电方法:此方法只能传入5v的电压。
public class Phone {
//充电方法
public void charging(IVoltage5V voltage5v) {
if(5 == voltage5v.output5v()) {
System.out.println("手机充电中");
}else {
System.out.println("电压不符合标准");
}
}
}
基本思路和类适配器相同,只是将 Adapter 类进行了修改,将原有的继承被适配类,修改为聚合的形式。使其持有 src 类的实例,以解决兼容性问题。这样也复核了“合成复用原则 OCP”(在系统中尽量使用关联代替继承关系),因此对象适配器模式是适配器模式常用的一种。
public class VoltageAdpter implements IVoltage5V{
//定义一个被适配类对象 聚合的方式
private Voltage220V voltage22ov;
//定义一个构造器,传入被适配的类
public VoltageAdpter(Voltage220V voltage22ov) {
this.voltage22ov = voltage22ov;
}
@Override
public int output5v() {
int dst = 0;
//获取目标输入的结果
if(voltage22ov != null) {
int src = voltage22ov.output();
dst = src/44;
}
//处理输入的电压
return dst;
}
}
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现方法(可以是空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。适用于一个类不想使用其接口中所有方法的情况。
【1】接口(被适配类)
public interface IAdpter {
public void method1();
public void method2();
}
【2】接口的抽象实现类(适配器)
public abstract class AdpterAbsClass implements IAdpter{
@Override
public void method1() {
}
@Override
public void method2() {
}
}
【3】目标类:重写自己需要的方法
public class TargetClass extends AdpterAbsClass{
//需要哪个方法重写哪个方法
@Override
public void method1() {
// TODO Auto-generated method stub
super.method1();
}
}
SpringMVC 中的 HandlerAdapter,就是用了适配器模式
【1】进入 SpringMVC 的 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 {
//通过 HandlerMapper 来映射 Controller
mappedHandler = getHandler(processedRequest);
// 获取适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//通过适配器调用controller的方法并返回 ModelAndView
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
}
【2】进入获取适配器的方法: getHandlerAdapter(…) 返回的 HandlerAdapter 是适配器接口,底层根据请求类型有多种实现(simpleControllerhandlerAdapter/HttpRequestHandlerAdapter 等等),显然如果要直接调用对应的 Controller 就需要使用判断语句。那么后面扩展 Controller 就需要修改控制层的代码,违反了 OCP 原则。因此下面 handlerAdapters 方法将获取所有的适配器实现类,循环调用适配器类的 supports 方法筛选出对应的适配器(return (handler instanceof Controller);)
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
【3】最终调用统一的业务处理方法:handle 进行业务逻辑处理,并最总返回视图对象。HandlerAdapter 接口的方法如下:
public interface HandlerAdapter {
//根据请求的类型,来判断是否为当前适配器的类型。如果是就返回当前适配器。
boolean supports(Object handler);
//业务处理逻辑方法入口
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
//与缓存相关,判断最后一次修改时间与浏览器的时间是否一致,一致则启用缓存。。。
long getLastModified(HttpServletRequest request, Object handler);
}
优点: ①、可以让任何两个没有关联的类一起运行。②、提高了类的复用。③、增加了类的透明度。④、灵活性好。
缺点: ①、过多地使用适配器,会让系统非常凌乱,不易整体进行把握。比如,我们调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不用适配器,而是直接对系统进行重构。②、如果使用类适配器只能适配一个适配者,而且目标类必须是抽象类。