源码分析之委派模式与适配器模式
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的编程模型。
以上是关于源码分析之委派模式与适配器模式的主要内容,如果未能解决你的问题,请参考以下文章