适配器设计模式

基本介绍

爱好数码产品/游戏主机的帅哥美女们应该知道,如果我们购买了一个港版或者美版/欧版的机器,那么我们的电源线一定是要使用转换插头(Adapter)才能让机器开机的,其转换插头就是就是适配器(Adapter)了,而我们今天讲解的就是适配器设计模式

  1. 适配器模式(Adapter Pattern)将某个类的接口转换为客户端期望的另一个接口表示,主要目的是兼容性,让原本因为接口不匹配不能一起工作的两个类可以协同工作,还有另一个叫法叫做:包装器(Wrapper,是不是在很多源码中看到过?)

  2. 适配器模式属于结构型模式

  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

工作原理

  1. 适配器模式:将一个接口转换成另一个接口,让原本不兼容的类可以兼容

  2. 从用户的角度看不到被适配者,是解耦的

  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配着的相关接口方法

  4. 用户收到反馈结果,感觉只是和目标接口交互

适配器三种模式

类适配器模式

模式的三种角色组成

  1. 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。

  2. 源(Adapee)角色:现在需要适配的接口。

  3. 适配器(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());
   }
 }
  1. 基本思路和类的适配器模式相同,只是将Adapter类做修改,不是继承Adapee源类,而是持有Adapee源类,以解决兼容性问题。即:持有Adapee源类,实现目标类接口,完成源类->目标类的适配

  2. 根据“合成复用”,在系统中尽量使用关联关系(聚合)来替代继承关系

  3. 对象适配器模式是适配器模式常用的一种

    小案例

    我们以生活中充电的例子来进行讲解,充电器本身相当于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;
       }
     }
    1. 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求

    2. 适用于一个接口不想使用其所有方法的情况

      小案例

      图解:

      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的逻辑,符合开闭原则。

      适配器模式的注意事项和细节

      1. 三种命名方式,是根据源类是以怎样的形式给到适配器Adapter来命名的。

      2. 三种模式总结:

        1. 类适配器:以类给到适配器中,就是将源类当作类,然后继承

        2. 对象适配器:以对象给到,在Adapter里,将源类当作一个对象,进行方法持有

        3. 接口适配器:以接口给到,在Adapter里,将源类作为一个接口,实现

      3. 最大的作用就是:将之前不兼容的的接口融合在一起工作。

      关于适配器模式的介绍就到这里了,有不足之处还望指出。

       

    基本介绍

    接口适配器模式(缺省适配器)

    1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同,根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承源类的局限性问题,也不再要求目标类是接口

    2. 使用成本更低,更加灵活

    注意事项和细节

    看完这个对象适配器是不是比以上更加灵活了呢?因为在适配器类中没有使用了继承,而是直接实现需要转换的接口重写接口的转换方法就可以了

    电压为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, 不能充电~~");
    		}
    	}
    }

基本介绍

对象适配器模式

  1. Java是单继承的机制,所有类适配器需要继承Adapee源角色,,因为这要求Target目标对象必须是接口,有一定的局限性。

  2. Adapee源类的方法在Adapter适配器中都会暴露出来,也增加了使用成本

  3. 由于继承了Adapee源类,所以它可以根据需求重写Adapee源类的方法,使得Adapter的灵活性增强了。

注意事项和细节

好了这就是一个基本的类适配器的使用,是不是很简单呢?但是呢,如果要使用类适配器模式的时候我们要

电压为5V,可以充电~~

输出结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值