设计模式(五)适配器模式Adapter(结构型)
一. 概述
接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化。
例子1:iphone4,你即可以使用UBS接口连接电脑来充电,假如只有iphone没有电脑,怎么办呢?苹果提供了iphone电源适配器。可以使用这个电源适配器充电。这个iphone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。)
例子2:最典型的例子就是很多功能手机,每一种机型都自带有充电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。
二. 问题
你如何避免因外部库的API改变而带来的不便?假如你写了一个库,你能否提供一种方法允许你软件的现有用户进行完美地升级,即使你已经改变了你的API?为了更好地适宜于你的需要,你应该如何改变一个对象的接口?
三. 解决方案
适配器(Adapter)模式为对象提供了一种完全不同的接口。你可以运用适配器(Adapter)来实现一个不同的类的常见接口,同时避免了因升级和拆解客户代码所引起的纠纷。
适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装(Wrapper)器模式(把已有的一些类包装起来,使之能有满足需要的接口)。
考虑一下当(不是假设!)一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。
四. 构建模式组成和实现方式
1、组成角色
•目标角色(Target):— 定义Client使用的与特定领域相关的接口。 • 客户角色(Client):与符合Target接口的对象协同。 • 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。 • 适配器角色(Adapter) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
2、共有两类适配器模式:
1.类的适配器模式(采用继承实现)2.对象适配器(采用对象组合方式实现)
1)类适配器模式 ——适配器继承需要适配的类(一般多重继承)。
类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示:
2)对象适配器模式—— 适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。
对象匹配器依赖于对象组合,如下图所示:
五. 应用场景
以下情况使用Adapter模式: 1 • 你想使用一个已经存在的类,而它的接口不符合你的需求。 2 • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 3 •(仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。即仅仅引入一个对象,并不需要额外的指针以间接取得adaptee。
六. 效果
类适配器和对象适配器有不同的权衡(此部分内容是《GOF设计模式》) 类适配器 • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。 • 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。 • 仅仅引入了一个对象,并不需要额外的指针以间接得到 Adaptee。
对象适配器则 • 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)—同时工作。Adapter也可以一次给所有的Adaptee添加功能。 • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
使用Adapter模式时需要考虑的其他一些因素有:
1) Adapter的匹配程度 对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名 )到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度 2) 可插入的Adapter 当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类, 就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去, 而这些系统对这个类的接口可能会有所不同。 3) 使用双向适配器提供透明操作 使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 Adaptee的接口, 因此并不是所有 Adaptee对象可以被使用的地方它都可以被使用。双向适配器提供了这样的透明性。 在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。
七. 实现
类适配器使用的是继承
让我们看看当API改变时,如何保护Client应用程序不受影响。
Target:
代码语言:javascript复制package com.demo.test.design;
/**
* Created by hguisu
*/
public class Target {
/**
* 这个方法将来有可能改进
*/
public void hello(){
System.out.println("Hello");;
}
/**
* 目标点
*/
public void world(){
System.out.println("'world'");;
}
}
Client:
代码语言:javascript复制package com.demo.test.design;
import java.io.IOException;
/**
* Created by huangguisu on 2020/7/21.
*/
public class ClientApp {
/**
* Main program.
*/
public static void main(String[] args) throws IOException {
Target target = new Target();
target.hello();
target.world();
}
}
我们Target已经明确指出hello()方法会在未来的版本中改进,甚至不被支持或者淘汰。接下来,现在假设第二版的Target已经发布。一个全新的greet()方法代替了hello()。
代码语言:javascript复制package com.demo.test.design;
/**
* Created by hguisu
*/
public class Target {
/**
* 这个方法将来有可能继续改进
*/
public void greet(){
System.out.println("'Greet '");;
}
/**
* 目标点
*/
public void world(){
System.out.println("'world'");;
}
}
如果我们继续使用原来的client代码,肯定会报错,找不到hello方法。针对API“升级”的解决办法就是创建一个适配器(Adapter)。
1、类适配器使用的是继承:
定义目标接口:
代码语言:javascript复制package com.demo.test.design;
/**
* 目标角色
* @version 2.0
* Created by huangguisu
*/
public interface TargetInterface {
/**
* 这个方法将来有可能继续改进
*/
public void hello();
/**
* 目标点
*/
public void world();
}
被适配的角色
代码语言:javascript复制package com.demo.test.design;
/**
* Created by hguisu
* 源角色:被适配的角色
*/
public class Adaptee {
/**
* 加入新的方法
*/
public void greet(){
System.out.println("'Greet '");;
}
/**
* 源类含有的方法
*/
public void world(){
System.out.println("'world'");;
}
}
类适配器角色
代码语言:javascript复制package com.demo.test.design;
/**
* Created by hguisu
* 类适配器角色
*/
public class Adapter extends Adaptee implements TargetInterface {
/**
* 源类中没有world方法,在此补充
*/
@Override
public void hello() {
super.greet();
}
}
客户端
代码语言:javascript复制package com.demo.test.design;
import java.io.IOException;
/**
* Created by hguisu
*/
public class ClientApp {
/**
* Main program.
*/
public static void main(String[] args) throws IOException {
TargetInterface target = new Adapter();
target.hello();
target.world();
}
}
2、对象适配器使用的是委派:
代码语言:javascript复制package com.demo.test.design;
/**
* Created by hguisu
* 类适配器角色
*/
public class Adapter2 implements TargetInterface {
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
adaptee = adaptee;
}
/**
* 源类中没有world方法,在此补充
*/
@Override
public void hello() {
adaptee.greet();
}
/**
* 源类中没有world方法,在此补充
*/
@Override
public void world() {
adaptee.world();
}
}
客户端:
代码语言:javascript复制package com.demo.test.design;
import java.io.IOException;
/**
* Created by hguisu
*/
public class ClientApp {
/**
* Main program.
*/
public static void main(String[] args) throws IOException {
// TargetInterface target = new Adapter();
// target.hello();
// target.world();
Adaptee adaptee = new Adaptee();
Adapter2 adapter = new Adapter2(adaptee);
adapter.hello();
adapter.world();
}
}
如例中代码所示,你可以运用适配器(Adapter)模式来避免因外部库改变所带来的不便——倘若向上兼容。作为某个库的开发者,你应该独立编写适配器,使你的用户更简便地使用新版本的库,而不用去修改他们现有的全部代码。
GoF书中提出的适配器(Adapter)模式更倾向于运用继承而不是组成。这在强类型语言中是有利的,因为适配器(Adapter)事实上是一个目标类的子类,因而能更好地与类中方法相结合。
为了更好的灵活性,我个人比较倾向于组成的方法(特别是在结合了依赖性倒置的情况下);尽管如此,继承的方法提供两种版本的接口,或许在你的实际运用中反而是一个提高灵活性的关键。
九、Spring MVC的适配模式
(20200721更新)
DispatcherServlet是Spring MVC的核心,处理请求的主要逻辑在方法doDispatch(request, response):
通过doDispatch(request, response)源码,我们可以清晰看到拦截器和controler具体实现原理和流程: 1)、获取HandlerExecutionChain,HandlerExecutionChain是对Controller和它的Interceptors的进行了包装。 2)、获取HandlerAdapter,即Handler对应的适配器。 3)、执行拦截器preHandle() 方法 4)、由HandlerAdapter的handle方法执行Controller的handleRequest,并且返回一个ModelAndView 5)、执行拦截器postHandle方法 6)、执行ModelAndView的render(mv, request, response)方法把合适的视图展现给用户; 7)、执行拦截器afterCompletion() 方法
代码语言:javascript复制protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//HandlerExecutionChain是对Controller和它的Interceptors的进行了包装;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//getHandler是从HandlerMappings中取出对应的handlerMapping对象,
//每个HandlerMapping对象代表一个Controller和URL的映射
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//获取HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if(isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if(this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" getRequestUri(request) "] is: " lastModified);
}
if((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//执行Controller的拦截器方法preHandle
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//由HandlerAdapter的handle方法执行Controller的handleRequest,并且返回一个ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if(asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//执行Controller的拦截器方法postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var19) {
dispatchException = var19;
}
//实际执行ModelAndView的render(mv, request, response)方法把合适的视图展现给用户;
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception var20) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
} catch (Error var21) {
this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
}
} finally {
if(asyncManager.isConcurrentHandlingStarted()) {
//执行Controller的拦截器方法afterCompletion
if(mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if(multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());采用了适配器模式.
1、使用适配器模式原因
Spring MVC控制器中Handler可以理解为Adaptee(被适配角色),实现方式有:Controller、HttpRequestHandler、Servlet、@RequestMapping。
由于Handler的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Handler方法,需要使用if else来进行判断是哪一种子类然后执行, 在doDispatch方法代码中写成如下形式:
代码语言:javascript复制mappedHandler = this.getHandler(processedRequest);
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
如果我们扩展了Handler,增加一个XXXXController, 是不是要在代码中加入一行 if(mappedHandler.getHandler() instanceof XXXXController)。这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 -- 对扩展开放,对修改关闭。
2、SpringMVC适配器模式
因此Spring创建了一个适配器接口(HandlerAdapter),使得每一种Handler有一种对应的适配器实现类, 让适配器代替Handler执行相应的方法。这样在扩展Handler时,只需要增加一个适配器类就完成了SpringMVC的扩展了
代码语言:javascript复制public interface HandlerAdapter {
boolean supports(Object var1);
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
supports()方法传入处理器Handler判断是否与当前适配器支持. 如果支持则从DispatcherServlet中的HandlerAdapter实现类中返回支持的适配器实现类。handle方法就是代理Handler来执行请求的方法并返回结果。
在DispatchServlert中的doDispatch方法中
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 来获取对应的HandlerAdapter 的实现子类,从而做到使得每一种Handler有一种对应的适配器实现类。
代码语言:javascript复制 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
Iterator var2 = this.handlerAdapters.iterator();
HandlerAdapter ha;
do {
if(!var2.hasNext()) {
throw new ServletException("No adapter for handler [" handler "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
ha = (HandlerAdapter)var2.next();
if(this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler adapter [" ha "]");
}
} while(!ha.supports(handler));
return ha;
}
getHandlerAdapter返回对应的适配实现类Handler来执行请求的方法handle:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
例如我们SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter) 类适配器处理,我们在xml配置适配器如下:
代码语言:javascript复制 <!--第三种:ControllerClassNameHandlerMapping 【控制器的类名处理映射】 不用配置访问路径,
默认的访问路径就是类名首字母大写变小写,加.do后缀 -->
<!--1.控制器的类名处理映射-->
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<!--2.配置控制器处理适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!--3.配置控制器 相当于配置了一个访问路径 1.name="/emo.do" 2.id="DemoController" -->
<bean class="com.demo.springmvc.controller.DemoController"></bean>
然后定义Controller(Handler)就可以实现org.springframework.web.servlet.mvc.Controller接口:
代码语言:javascript复制import org.springframework.web.servlet.mvc.Controller;
public class DemoController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView("demo");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
mav.addObject("message", "spring MVC");
return mav;
}
}
3、模拟springMVC适配器模式:
定义接口Contrller接口:
代码语言:javascript复制package com.demo.test.springmvc;
/**
* Created by huangguisu on 2020/7/21.
*/
public interface Controller {
public void handleRequest();
}
三种Controller实现:
代码语言:javascript复制package com.demo.test.springmvc;
/**
* Created by hguisu
*/
public class HttpController implements Controller{
@Override
public void handleRequest(){
System.out.println("demo...");
}
}
public class SimpleController implements Controller{
public void doSimplerHandler(){
System.out.println("simple...");
}
}
public class AnnotationController implements Controller{
public void doAnnotationHandler(){
System.out.println("annotation...");
}
}
代码语言:javascript复制handlermaping实现:
代码语言:javascript复制package com.demo.test.springmvc;
import java.util.ArrayList;
import java.util.List;
public interface HandlerMapping {
Controller getHandler(String var1) ;
}
public class SimleHandlerMapping implements HandlerMapping {
//Controller
public static List<Controller> handlers = new ArrayList<Controller>();
@Override
public Controller getHandler(String prefixBean) {
String bean = prefixBean;
if (bean.toLowerCase().equals("demo")) {
return new SimpleController();
}
return null;
}
}
代码语言:javascript复制handler适配器接口:
代码语言:javascript复制package com.demo.test.springmvc;
/**
* Created by huangguisu
*/
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}
handler适配器实现:
代码语言:javascript复制package com.demo.test.springmvc;
/**
* Created by hguisu on 2020/7/21.
*/
public class HttpHandlerAdapter implements HandlerAdapter {
@Override
public void handle(Object handler) {
((HttpController)handler).handleRequest();
}
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
public class SimpleHandlerAdapter implements HandlerAdapter{
@Override
public void handle(Object handler) {
((HttpController)handler).handleRequest();
}
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
public class AnnotationHandlerAdapter implements HandlerAdapter {
@Override
public void handle(Object handler) {
((AnnotationController)handler).handleRequest();
}
@Override
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
DispatchServlet适配器实现:
代码语言:javascript复制package com.demo.test.springmvc;
/**
* Created by hguisu
*/
import java.util.*;
public class DispatchServlet {
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
public static List<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>();
//
public DispatchServlet(){
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
handlerMappings.add(new SimleHandlerMapping());
}
public void doDispatch(String request){
//SpringMVC从request取handler的对象,一般都是Srping容器来管理
// mappedHandler = this.getHandler(processedRequest);
Controller controller = getHandler( request);
//得到对应适配器
HandlerAdapter adapter = getHandlerAdapter(controller);
//通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandlerAdapter(Controller controller){
for(HandlerAdapter adapter: handlerAdapters){
try {
if(adapter.supports(controller)){
return adapter;
}
}catch (Exception e) {
}
}
return null;
}
protected Controller getHandler(String request) {
Controller handler = null;
for(HandlerMapping handlerMapping: handlerMappings){
handler = handlerMapping.getHandler(request);
if (handler != null) {
break;
}
}
return handler;
}
public static void main(String[] args){
new DispatchServlet().doDispatch("Demo");
}
}
十、适配器模式与其它相关模式
桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口 装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是decorator模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。 Facade(外观模式):适配器模式的重点是改变一个单独类的API。Facade的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。 适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。 代理模式(Proxy )在不改变它的接口的条件下,为另一个对象定义了一个代理。 装饰者模式,适配器模式,外观模式三者之间的区别: 装饰者模式的话,它并不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。 适配器模式是将一个接口通过适配来间接转换为另一个接口。 外观模式的话,其主要是提供一个整洁的一致的接口给客户端。