源码分析之委派模式与适配器模式

Posted 学无止路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析之委派模式与适配器模式相关的知识,希望对你有一定的参考价值。

1.1适配器模式

结构型:从程序的结构上实现松耦合,从而扩大整体的类结构,用来解决更多更大的问题。

适配器模式(Adapter Pattern) 将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。

适配器模式属于结构型模式。

主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。如图所示:

适配器模式的角色划分

Target目标抽象类 :定义客户所需的接口,可以是抽象类或者接口,也可以是具体类(5V电压)。

Adapter适配器 :适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,

适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系( 充电器  --》转换适配作用)。

Adaptee 适配者类:被适配的角色 。(220V电压)

原理分析

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

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

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

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

Java代码如下:

package com.txw.adapter.classadapter;

 

/

   @Author:AdAir

 /

//被适配的类  墙上的插槽

@SuppressWarnings("all")       // 注解警告信息

public class Voltage220V {

     //输出220V的电压

     public int output220V() {

         int src = 220;

         System.out.println("电压=" + src +  "伏");

         return src;

     }

}

package com.txw.adapter.classadapter;

 

/

    @Author:AdAir

 /

//适配接口   手机端插槽接口

@SuppressWarnings("all")       // 注解警告信息

public interface IVoltage5V {

     public int output5V();

}

package com.txw.adapter.classadapter;

 

/

   @Author:AdAir

 /

@SuppressWarnings("all")       // 注解警告信息

public class Phone {

     //充电

     public void getPower(IVoltage5V iVoltage5V) {

         if(iVoltage5V.output5V() == 5) {

            System.out.println("电压为5V, 可以充电~~");

         } else if (iVoltage5V.output5V() > 5) {

            System.out.println("电压大于5V, 不能充电~~");

         }

     }

}

 

package com.txw.adapter.classadapter;

 

/

   @Author:AdAir

 /

//适配器类

@SuppressWarnings("all")       // 注解警告信息

public class VoltageAdapter extends  Voltage220V implements IVoltage5V {

     //  处理请求,让 200V的电降至 5V

     @Override

     public int output5V() {

         // TODO Auto-generated method stub

         //获取到220V电压

         int srcV = output220V();

         int dstV = srcV / 44; //转成 5v

         return dstV;

     }

}

 

package com.txw.adapter.classadapter;

 

/

   @Author:AdAir

 /

@SuppressWarnings("all")       // 注解警告信息

public class Client {

     public static void main(String[] args) {

         System.out.println(" === 类适配器模式  ====");

         Phone phone = new Phone();

         phone.getPower(new VoltageAdapter());

     }

}

/

 总结: 合成复用(组合优于继承原则)

      类适配器 违背了合成复用原则,在系统中尽量的使用关联关系来替代继承关系。

 /

1.2 对象适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有 src类,实现 dst 类接口,完成src->dst的适配。

 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。

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

Java代码如下:

package com.txw.adapter.objectadapter;

 

/

 @Author:Adair

 /

//适配接口

@SuppressWarnings("all")     // 注解警告信息

public interface IVoltage5V {

     public int output5V();

}

 

package com.txw.adapter.objectadapter;

 

/

 @Author:Adair

 /

//被适配的类

@SuppressWarnings("all")     // 注解警告信息

public class Voltage220V {

     //输出220V的电压  不变

     public int output220V() {

         int src = 220;

         System.out.println("电压=" + src +  "伏");

         return src;

     }

}


package com.txw.adapter.objectadapter;

 

/

 @Author:Adair

 /

//适配器类  实现目标接口

@SuppressWarnings("all")     // 注解警告信息

public class VoltageAdapter  implements IVoltage5V {

     // 关联-聚合关系

     private Voltage220V   voltage220V;

     public VoltageAdapter(Voltage220V   voltage220V){

         this.voltage220V = voltage220V;

     }

     @Override

     public int output5V() {

         int dstV = 0;

         if (null != voltage220V){

            //获取220V 电压

            int src =  voltage220V.output220V();

            System.out.println("使用对象适配器,进行适配。。");

            dstV = src/44;

            System.out.println("适配完成,输出的电压为--->" + dstV);

         }

         return dstV;

     }

}

 

package com.txw.adapter.objectadapter;

 

/

 @Author:Adair

 /

@SuppressWarnings("all")     // 注解警告信息

public class Phone {

     //充电

     public void getPower(IVoltage5V iVoltage5V) {

         if(iVoltage5V.output5V() == 5) {

            System.out.println("电压为5V, 可以充电~~");

         } else if (iVoltage5V.output5V() > 5) {

            System.out.println("电压大于5V, 不能充电~~");

         }

     }

}

客户端测试代码如下:

package com.txw.adapter.objectadapter;

 

/

 @Author:Adair

 /

@SuppressWarnings("all")     // 注解警告信息

public class Client {

     public static void main(String[] args) {

         System.out.println(" === 对象适配器模式  ====");

         Phone phone = new Phone();

         // 继承变 聚合

         phone.getPower(new VoltageAdapter(new Voltage220V()));

     }

}

/

     对象适配器:

      1.一个对象适配器 可以把多个不同的适配器适配到同一个目标。

      2.对象适配器复合,合成复用原则。

      3.可以适配一个适配者的子类,由于适配器与适配者是关联关系,

      根据“里氏替换原则”,适配者的子类也可以通过该适配器进行适配。

      应用场景如下:

      两个原本不相干的类/方法/接口,通过适配器来建立联系或者适配器来兼容这两个类。

      旧系统的版本更迭   --》适配器做部分升级,

      创建一些重复使用的类,用于与一些彼此之间没有太多关联的一些类,引入适配器来一起进行工作。

 /

应用场景的java代码如下:

package com.txw.adapter.user;

 

/

   TODO {@link UserInfo}

   @Author:Adair

 /

@SuppressWarnings("all")       // 注解警告信息

public class UserInfo {

     private String username;

     private String password;

     private String mid;

     private String info;

     public String getUsername() {

         return username;

     }

     public void setUsername(String username) {

         this.username = username;

     }

     public String getPassword() {

         return password;

     }

     public void setPassword(String password) {

         this.password = password;

     }

     public String getMid() {

         return mid;

     }

     public void setMid(String mid) {

         this.mid = mid;

     }

     public String getInfo() {

         return info;

     }

     public void setInfo(String info) {

         this.info = info;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link SingleService}

   @Author:Adair

 /

//    单一的注册登录方式(被适配类Adaptee)

@SuppressWarnings("all")       // 注解警告信息

public class SingleService {

     /

       注册方法

       @param username

       @param password

       @return

      /

     public ResultMsg regist(String username, String password) {

         return new ResultMsg(200, "注册成功",  new UserInfo());

     }

     /

       登录的方法

       @param username

       @param password

       @return

      /

     public ResultMsg login(String username, String password) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link SuppotPortForThirdAdapter}

   @Author:Adair

  支持最简单的登录方式

   还兼容第三方的登录方式

 /

@SuppressWarnings("all")       // 注解警告信息

public class SuppotPortForThirdAdapter  extends SingleService implements ISuppotPortForThird {

     /

      

       @param id

       @return ResultMsg

      /

     @Override

     public ResultMsg loginForQQ(String id) {

         return processLogin(id, LoginForQQAdapter.class);

     }

     /

      

       @param id

       @return ResultMsg

      /

     @Override

     public ResultMsg loginForWechat(String id) {

         return processLogin(id, LoginForWechatAdapter.class);

     }

     /

      

       @param token

       @return ResultMsg

      /

     @Override

     public ResultMsg loginForToken(String token) {

         return processLogin(token, LoginForTokenAdapter.class);

     }

     /

      

       @param telphone

       @param code

       @return ResultMsg

      /

     @Override

     public ResultMsg loginForTelphone(String telphone, String code) {

         return processLogin(telphone, LoginForTelAdapter.class);

     }

     /

      

       @param username

       @param passport

       @return ResultMsg

      /

     @Override

     public ResultMsg loginForRegist(String username, String passport) {

         super.regist(username, null);

         return super.login(username, null);

     }

    //  兼容登录方式

     private ResultMsg processLogin(String key, Class<? extends  LoginAdapter> clazz) {

         try {

            LoginAdapter adapter =  clazz.newInstance();

             //判断是否是支持的登录方式,登录需要对应的适配器执行登录方法

                if (adapter.support(adapter))  {

                    System.out.println("传入参数是: key"+"; 调用  "+adapter+" 的login方法");

                    return adapter.login(key,  adapter);

                } else {

                return null;

            }

         } catch (Exception e) {

            e.printStackTrace();

         }

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link ResultMsg}

   @Author:Adair

   Title: ResultMsg

   Description: 统一返回格式

 /

@SuppressWarnings("all")       // 注解警告信息

public class ResultMsg {

     private int code;

     private String msg;

     private Object data;

     public ResultMsg(int code, String msg, Object data) {

         this.code = code;

         this.msg = msg;

         this.data = data;

     }

     public int getCode() {

         return code;

     }

     public void setCode(int code) {

         this.code = code;

     }

     public String getMsg() {

         return msg;

     }

     public void setMsg(String msg) {

         this.msg = msg;

     }

     public Object getData() {

         return data;

     }

     public void setData(Object data) {

         this.data = data;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginForWechatAdapter}

   @Author:Adair

   微信登录

 /

@SuppressWarnings("all")       // 注解警告信息

public class LoginForWechatAdapter  implements LoginAdapter{

     @Override

     public boolean support(Object adapter) {

         return adapter instanceof LoginForWechatAdapter;

     }

     @Override

     public ResultMsg login(String id, Object adapter) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginForTokenAdapter}

   @Author:Adair

    token自动登录

 /

@SuppressWarnings("all")       // 注解警告信息

public class LoginForTokenAdapter  implements LoginAdapter{

     @Override

     public boolean support(Object adapter) {

         return adapter instanceof LoginForTokenAdapter;

     }

     @Override

     public ResultMsg login(String id, Object adapter) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginForTelAdapter}

   @Author:Adair

  手机登录

 /

@SuppressWarnings("all")       // 注解警告信息

public class LoginForTelAdapter  implements LoginAdapter {

     @Override

     public boolean support(Object adapter) {

         return  adapter instanceof  LoginForTelAdapter;

     }

     @Override

     public ResultMsg login(String id, Object adapter) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginForSinaAdapter}

   @Author:Adair

  新浪微博登录

 /

@SuppressWarnings("all")       // 注解警告信息

public class LoginForSinaAdapter  implements LoginAdapter {

     @Override

     public boolean support(Object adapter) {

         return adapter instanceof LoginForSinaAdapter;

     }

     @Override

     public ResultMsg login(String id, Object adapter) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginForQQAdapter}

   @Author:Adair

    QQ登录

 /

@SuppressWarnings("all")       // 注解警告信息

public class LoginForQQAdapter implements  LoginAdapter{

     @Override

     public boolean support(Object adapter) {

         return adapter instanceof LoginForQQAdapter;

     }

     @Override

     public ResultMsg login(String id, Object adapter) {

         return null;

     }

}

 

package com.txw.adapter.user;

 

/

   TODO {@link LoginAdapter}

   @Author:Adair

  登录适配器接口

 /

@SuppressWarnings("all")       // 注解警告信息

public interface LoginAdapter {

     // 是否支持的登录方式

     boolean support(Object adapter);

     // 登录

     ResultMsg login(String id, Object adapter);

     // 适配器执行登录方法

}

 

package com.txw.adapter.user;

 

/

   @Author:Adair

   Title: IPassportForThird

   Description: 第三方登录兼容接口

 /

@SuppressWarnings("all")       // 注解警告信息

public interface ISuppotPortForThird {

     /

       QQ 登录

       @param id

       @return

      /

     ResultMsg loginForQQ(String id);

     /

       微信登录

       @param id

       @return

      /

     ResultMsg loginForWechat(String id);

     /

       记住登录状态后自动登录

       @param token

       @return

      /

     ResultMsg loginForToken(String token);

     /

       手机号登录

       @param telphone

       @param code

       @return

      /

     ResultMsg loginForTelphone(String telphone, String code);

     /

       注册后自动登录

       @param username

       @param passport

       @return

      /

     ResultMsg loginForRegist(String username, String passport);

}

 

客户端测试的代码如下:

package com.txw.adapter.user;

 

/

   TODO {@link Client}

 

   @Author:Adair

 /

@SuppressWarnings("all")       // 注解警告信息

public class Client {

     public static void main(String[] args) {

         // 第三方兼容接口

         ISuppotPortForThird passportForThird = new  SuppotPortForThirdAdapter();

         //原来的接口 可用

         passportForThird.loginForRegist("admin", "admin");

         //新增适配的接口 同样可用

         passportForThird.loginForQQ("mars");

     }

}

应用场景流程图如下:

源码分析之委派模式与适配器模式

源码分析之委派模式与适配器模式



适配器应用 如图所示:

源码分析之委派模式与适配器模式


适配器应用

分析SpringMVC步骤如下:

点两下Shift键,并输入DispatcherServlet如图所示:

源码分析之委派模式与适配器模式


查找 doDispatch方法,如图所示:

源码分析之委派模式与适配器模式


doDispatch()  --> 分发请求,请求需要找到对应匹配的适配器来处理。

 不同的映射处理器(HandlerMapper)映射出来的handler对象是不一样。

 不同类型的handler通过不同的方法来对请求进行处理(HandlerMathod)

 不同的handler使用不同的适配器(适配器调用handler)

 HandlerAdapter作用:拿到对应的handler去执行对应的目标方法

 HandlerMapping (Spring5 URL controller 方法名称) 

 getHandlerAdapter()遍历所有的HandlerAdapter,通过suppots判断找到匹配的适配器。

//  判断有没有需要执行的拦截器

if (!mappedHandler.applyPreHandle(processedRequest, response)) {                                      return;

              }

   //   适配器去执行Handler

// Actually invoke the handler.

mv = ha.handle(processedRequest, response,mappedHandler.getHandler());

总结:1. DispatcherServlte会根据handlerMapping传过来的controller(Hnadler)与已经注册好了的HandlerAdapter一一匹配,看哪一种HandlerAdapter是支持

该Handler类型的,如果找到了其中一种HandlerAdapter是支持传过来

的Handler类型,那么该HandlerAdapter会调用自己的handle方法,

handle方法运用java的反射机制执行Handler的具体方法来获得ModelAndView,

 2. HandlerAdapter有几个子类,每个子类都是适配某一种类型的控制器(Handler),

   有了HandlerAdapter,你只需要调用handle方法,屏蔽了不一致的细节,否则在DispatcherServlet里面要if else if else了。

1.3 委派模式

在常用的23种设计模式中其实面没有委派模式(delegate)的影子,但是在Spring中委派模式确实用的比较多的一种模式。归属于行为型模式。

在spring中的体现:Spring MVC框架中的DispatcherServlet其实就用到了委派模式。

委派模式的作用:基本作用就是负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理的全权代理,但是代理模式注重过程,而委派模式注重结果。

委派模式:基本作用就是负责任务的调用和分配任务,跟代理模式很像,

          可以看做是一种特殊情况下的静态代理的全权代理,

          但是代理模式注重过程,而委派模式注重结果。

 委派模式的角色扮演:

抽象任务角色  --》分发的任务

委派者角色     --》 接收任务  分发任务给具体任务角色,自己本身不做事

具体任务角色  --》 普通员工 (做事的真实角色)如图所示:


委派的应用:

   SpringMVC:DispatcherServlet

   BeanDefinition --> Bean注册

  DefaultBeanDefinitionDocumentReader

Java代码如下:

package com.txw.delegate.v1;

 

/**

 *  @Project: spring

 *  @description: 执行的接口

 *  @ModificationHistory who    when  What

 **/

//抽象任务角色

@SuppressWarnings("all")        // 注解警告信息

public interface IExcuter {

     void excute(String command);

}

 

package com.txw.delegate.v1;

 

import java.util.HashMap;

import java.util.Map;

/**

 *  TODO {@link Leader}

 *  @Author:Adair

 */

//leader 委派者   接收任务,任务分发的作用

@SuppressWarnings("all")        // 注解警告信息

public class Leader implements  IExcuter {

     private Map<String,IExcuter> targets = new HashMap<String,IExcuter>();

     public Leader() {

         targets.put("加密",new ExcuterA());

         targets.put("登录",new ExcuterB());

     }

     @Override

     public void excute(String command) {

         targets.get(command).excute(command);

     }

}

 

package com.txw.delegate.v1;

 

/**

 *  TODO {@link ExcuterA}

 *  @Author:Adair

 */

//员工A执行某项命令

//具体任务角色

@SuppressWarnings("all")        // 注解警告信息

public class ExcuterA implements  IExcuter{

     @Override

     public void excute(String command) {

         System.out.println("员工A 开始做"+command+"的工作");

     }

}

 

package com.txw.delegate.v1;

 

/**

 *  TODO {@link ExcuterB}

 *  @Author:Adair

 */

//员工B执行某项命令

@SuppressWarnings("all")        // 注解警告信息

public class ExcuterB implements  IExcuter{

     @Override

     public void excute(String command) {

         System.out.println("员工B 开始做"+command+"的工作");

     }

}

客户端测试代码如下:

package com.txw.delegate.v1;

 

/**

 *  TODO {@link Boss}

 *  @Author:Adair

 */

// boss   模拟客户执行任务

@SuppressWarnings("all")        // 注解警告信息

public class Boss {

     public static void main(String[] args) {

         //直接找委托者

         Leader leader  = new Leader();

         //看上去好像是我们的项目经理在干活

         //但实际干活的人是普通员工

         //这就是典型,干活是我的,功劳是你的

         leader.excute("登录");

         leader.excute("加密");

     }

}

第二版本的代码如下:

依赖jar包的代码如下:

   <dependency>

            <!--导入spring-context的依赖jar包-->

             <groupId>javax.servlet</groupId>

             <artifactId>servlet-api</artifactId>

             <version>2.3</version>

         </dependency>

 

package com.txw.delegate.v2;

 

/**

 *  TODO {@link RequestAction}

 *  @Author:Adair

 */

// controller层

@SuppressWarnings("all")        // 注解警告信息

public class RequestAction {

     public void getHandleById(String mid){

     }

}

模拟DispatcherServlet的代码如下:

package com.txw.delegate.v2;

 

import  javax.servlet.http.HttpServletRequest;

import  javax.servlet.http.HttpServletResponse;

import  java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.util.ArrayList;

import java.util.List;

/**

 *  TODO {@link DispatcherServletClone}

 *  @Author:Adair

 */

// 模拟DispatcherServlet

// selvelt的任务分发者,主要完成url的映射和调用(Url --> Method -->  Handler)

@SuppressWarnings("all")        // 注解警告信息

public class DispatcherServletClone {

     //这里也可以用map 对象来保存Hanlder对象

     private List<Handler> handlerMapping = new  ArrayList<Handler>();

     public DispatcherServletClone() {

         //简单实现一个controller的映射

         try {

            Class clazz  = RequestAction.class;

            handlerMapping.add(new Handler()

                     .setController(clazz.newInstance())

                     .setMethod(clazz.getMethod("getHandleById",new  Class[]{String.class}))

                     .setUrl("/web/getHandleById.json")

            );

         } catch (Exception e) {

             e.printStackTrace();

         }

     }

     private void   doService(HttpServletRequest request, HttpServletResponse response){

         doDispatch(request,response);

     }

     /**

      * 请求的分发工作

      * @param request

      * @param response

      */

     private void doDispatch(HttpServletRequest request,  HttpServletResponse response) {

         //1.获取用户请求的url

         String url = request.getRequestURI();

         Handler handler =null;

 

         ////2、根据url 去handlerMapping找到对应的handler

         for(Handler h :handlerMapping){

            if(url.equals(h.getUrl())){

                handler = h;

                break;

            }

         }

         //3.将具体的任务分发给Method(通过反射去调用其对应的方法)

         Object obj = null;

         try {

            obj =   handler.getMethod().invoke(handler.getController(),request.getParameter("mid"));

         } catch (IllegalAccessException e) {

            e.printStackTrace();

         } catch (InvocationTargetException e) {

            e.printStackTrace();

         }

         //4、获取到Method执行的结果,通过Response返回出去

         // response.getWriter().write();

     }

     /**

      * 具体的hanlder对象

      */

     class Handler{

         //controller对象

         private Object controller;

         //controller对象映射的方法

         private  String url;

         //url对应的方法

         private Method method;

         public Object getController() {

            return controller;

         }

         public Handler setController(Object controller) {

            this.controller = controller;

            return this;

         }

         public String getUrl() {

            return url;

         }

         public Handler setUrl(String url) {

            this.url = url;

            return  this;

         }

         public Method getMethod() {

             return method;

         }

         public Handler setMethod(Method method) {

            this.method = method;

            return this;

         }

     }

}

1.4 思一思

 Controller层`配置的url`如何跟`具体的方法`映射的,`参数`又是如何`绑定`的?

思路:Java NIO的核心类库多路复用器Selector就是基于epoll的多路复用技术实现的

 

考虑:项目Leader角色?实际任务角色?分发过程?抽象角色?

PS:NIO2.0提供了异步文件通道和异步套接字通道的实现。类似于Unix网络编程中的事件驱动I/O - AIO。它不需要通过多路复用器-Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。

以上是关于源码分析之委派模式与适配器模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之适配器模式与桥接模式详解和应用

设计模式结构型模式

其他设计模式

适配器模式+源码分析

设计模式 之 适配器模式与外观模式

设计模式之委派模式与模板模式详解和应用