基本介绍
爱好数码产品/游戏主机的帅哥美女们应该知道,如果我们购买了一个港版或者美版/欧版的机器,那么我们的电源线一定是要使用转换插头(Adapter)才能让机器开机的,其转换插头就是就是适配器(Adapter)了,而我们今天讲解的就是适配器设计模式
-
适配器模式(Adapter Pattern)将某个类的接口转换为客户端期望的另一个接口表示,主要目的是兼容性,让原本因为接口不匹配不能一起工作的两个类可以协同工作,还有另一个叫法叫做:包装器(Wrapper,是不是在很多源码中看到过?)
-
适配器模式属于结构型模式
-
主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
-
适配器模式:将一个接口转换成另一个接口,让原本不兼容的类可以兼容
-
从用户的角度看不到被适配者,是解耦的
-
用户调用适配器转化出来的目标接口方法,适配器再调用被适配着的相关接口方法
-
用户收到反馈结果,感觉只是和目标接口交互
适配器三种模式
类适配器模式
模式的三种角色组成
-
目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
-
源(Adapee)角色:现在需要适配的接口。
-
适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
小案例
我们以生活中充电的例子来进行讲解,充电器本身相当于Adapter,我们家用电压为220V(Adapee,作为被适配的对象),我们的目标是转换为5V(Target)手机充电电压,使用类适配器模式完成
图解:
代码:
//适配接口
public interface IVoltage5V {
public int output5V();
}
//适配接口
public interface IVoltage5V {
public int output5V();
}
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
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, 不能充电~~");
}
}
}
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, 不能充电~~");
}
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
-
基本思路和类的适配器模式相同,只是将Adapter类做修改,不是继承Adapee源类,而是持有Adapee源类,以解决兼容性问题。即:持有Adapee源类,实现目标类接口,完成源类->目标类的适配
-
根据“合成复用”,在系统中尽量使用关联关系(聚合)来替代继承关系
-
对象适配器模式是适配器模式常用的一种
小案例
我们以生活中充电的例子来进行讲解,充电器本身相当于Adapter,我们家用电压为220V(Adapee,作为被适配的对象),我们的目标是转换为5V(Target)手机充电电压,使用对象适配器模式完成
图解
//适配接口 public interface IVoltage5V { public int output5V(); }
//适配接口 public interface IVoltage5V { public int output5V(); }//被适配的类 public class Voltage220V { //输出220V的电压,不变 public int output220V() { int src = 220; System.out.println("电压=" + src + "伏"); return src; } }
//被适配的类 public class Voltage220V { //输出220V的电压,不变 public int output220V() { int src = 220; System.out.println("电压=" + src + "伏"); return src; } }//适配器类 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; } }
//适配器类 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; } }-
核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
-
适用于一个接口不想使用其所有方法的情况
小案例
图解:
public interface Interface4 { public void m1(); public void m2(); public void m3(); public void m4(); }
//在AbsAdapter 我们将 Interface4 的方法进行默认实现 public abstract class AbsAdapter implements Interface4 { //默认实现 public void m1() { } public void m2() { } public void m3() { } public void m4() { } }
public class Client { public static void main(String[] args) { AbsAdapter absAdapter = new AbsAdapter() { //只需要去覆盖我们 需要使用 接口方法 @Override public void m1() { System.out.println("使用了m1的方法"); } }; absAdapter.m1(); } }
适配器模式在springmvc中的分析
这里扩展一下适配器模式的知识点,想必做过Java开发的都知道SpringMVC,这套框架可以帮助我们把前端的请求访问到后台对应的controller的方法上,然后再把处理结果返回给后端,它的底层其实就用到了适配器模式。
SpringMVC中的适配器模式主要用于执行目标Controller中的请求处理方法。在它的底层处理中,DispatcherServlet作为用户,HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,那样每增加一个类型的Controller就需要使用增加一个if else判断instance of,这违反了设计模式的开闭原则 —— 对扩展开放,对修改关闭。
类图分析
DispatchServlet中首先得到一个Controller类型,我们通过该Controller类型来获取HandlerAdapter的对应适配器类型,得到这个适配器之后我们就可以调用对应的Controller的doHandler()方法
源码讲解
那么SpringMVC是怎么处理的呢?我们来简单看一下源码,首先是适配器接口HandlerAdapter
public interface HandlerAdapter { boolean supports(Object var1); ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); }
该接口的适配器类对应着 Controller,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。举个例子,有一个适配器类
HttpRequestHandlerAdapter
,该类就是实现了HandlerAdapter接口,这是它的源码:public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; } }
当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的
handle
方法来调用Controller中的用于处理请求的方法。public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //......................... //找到匹配当前请求对应的适配器 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 (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); }finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } } // 遍历集合,找到合适的匹配器 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"); } }
这样一来,所有的controller就都统一交给HandlerAdapter处理,免去了大量的 if-else 语句判断,同时增加controller类型只需增加一个适配器即可,不需要修改到Servlet的逻辑,符合开闭原则。
适配器模式的注意事项和细节
-
三种命名方式,是根据源类是以怎样的形式给到适配器Adapter来命名的。
-
三种模式总结:
-
类适配器:以类给到适配器中,就是将源类当作类,然后继承
-
对象适配器:以对象给到,在Adapter里,将源类当作一个对象,进行方法持有
-
接口适配器:以接口给到,在Adapter里,将源类作为一个接口,实现
-
-
最大的作用就是:将之前不兼容的的接口融合在一起工作。
关于适配器模式的介绍就到这里了,有不足之处还望指出。
-
基本介绍
接口适配器模式(缺省适配器)
-
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同,根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承源类的局限性问题,也不再要求目标类是接口
-
使用成本更低,更加灵活
注意事项和细节
看完这个对象适配器是不是比以上更加灵活了呢?因为在适配器类中没有使用了继承,而是直接实现需要转换的接口重写接口的转换方法就可以了
电压为5V, 可以充电~~
输出结果:
public class Client { public static void main(String[] args) { Phone phone = new Phone(); phone.charging(new VoltageAdapter(new Voltage220V())); } }
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, 不能充电~~"); } } }
-
基本介绍
对象适配器模式
-
Java是单继承的机制,所有类适配器需要继承Adapee源角色,,因为这要求Target目标对象必须是接口,有一定的局限性。
-
Adapee源类的方法在Adapter适配器中都会暴露出来,也增加了使用成本
-
由于继承了Adapee源类,所以它可以根据需求重写Adapee源类的方法,使得Adapter的灵活性增强了。
注意事项和细节
好了这就是一个基本的类适配器的使用,是不是很简单呢?但是呢,如果要使用类适配器模式的时候我们要
电压为5V,可以充电~~
输出结果