设计模式之委派模式及适配器模式
委派模式:
委派模式不属于23种设计模式, 主要角色有三种: 抽象任务角色, 委派者角色, 具体任务角色.实现层面上, 定义一个抽象接口, 它有若干实现类, 他们真正执行业务方法, 这些子类是具体任务角色; 定义委派者角色也实现该接口, 但它负责在各个具体角色实例之间做出决策, 由它判断并调用具体实现的方法.委派模式对外隐藏了具体实现, 仅将委派者角色暴露给外部, 在spring中以 Delegate与 Dispatcher结尾的,都是委派模式。其实委派模式就是代理模式跟策略模式的一种特殊的组合情况。
应用场景:不属于 23 种设计模式之一,是面向对象设计模式中常用的一种模式。这种模式的原理为类 B和类 A 是两个互相没有任何关系的类,B 具有和 A 一模一样的方法和属性;并且调用 B 中的方法,属性就是调用 A 中同名的方法和属性。B 好像就是一个受 A 授权委托的中介。第三方的代码不需要知道 A 的存在,也不需要和 A 发生直接的联系,通过 B 就可以直接使用 A 的功能,这样既能够使用到 A 的各种功能,又能够很好的将 A 保护起来了,一举两得。
意图:
定义抽象接口的一个实现类, 他负责判断和调用哪个实现类的实例。
优点: 对内隐藏实现, 易于扩展; 简化调用;
委派模式大量使用在spring,mybatis等开源框架中, 理解委派模式的实现原理可以更好理解这些框架源码.委派模式的核心是委派类的实现.
如Spring的DispatcherServlet.其实真的去执行方法的人并不是这个 DispatcherServlet,而实他通过handle中预先加载了各个contriller中method对应的URL,保存在内存中,当有请求过来,他拿到这个请求所对应的 URL,然后去比较,找到对应的就交给对应的Method去执行。
这里举个简单的例子,通过 Boss去交代一个任务给项目经理, 项目经理(Leader)自己不干活,但是他知道可以由谁去干这个活,结果分配到手底下的人去做。
先定义一个抽象类:
public interface ITarget { //具体需要谁去干,由子类(项目经理)去决定 // 需要怎么干,由员工定义 void doing(String command); }
先来两个员工(被委派者(Target)):
public class TargetA implements ITarget { @Override public void doing(String command) { System.out.println("我是员工A,我现在开始干" + command + "工作"); } } public class TargetB implements ITarget { @Override public void doing(String command) { System.out.println("我是员工B,我现在开始干" + command + "工作"); } }
项目经理(委派者(Leader))登场:
public class Leader implements ITarget { //作为委派者,他必须知道底下的人谁比较擅长干什么 // 就好比DispatcherServlet 里面哪个handle保存了 controller跟URL的对应关系一样 private Map<String,ITarget> targets = new HashMap<String,ITarget>(); public Leader() { // 员工A适合干加密工作 targets.put("加密",new TargetA()); // 员工B 适合干登陆开发 targets.put("登录",new TargetB()); } //项目经理自己不干活 public void doing(String command){ targets.get(command).doing(command); } }
最后来个测试类,Boss 来发布任务了,这样子项目经理会委派底下的人去干活,而自己干了一下调度的活。
public class Boss { public static void main(String[] args) { //客户请求(Boss)、委派者(Leader)、被委派者(Target) //委派者要持有被委派者的引用 //代理模式注重的是过程, 委派模式注重的是结果 //策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用 //委派的核心:就是分发、调度、派遣 //委派模式:就是静态代理和策略模式一种特殊的组合 new Leader().doing("登录"); } }
delegate委派模式和Proxy代理模式:
Proxy :译为代理,主要使用场景:为简化编程(或无法操作B)而把请求交给代理方(A),由代理方与被代理方进行通信,以完成请求。
Delegete : 译为委托,主要使用场景:一件事情(或一个请求)对象本身不知道怎样处理,对象把请求交给其它对象来做。
简单来讲,可以这么理解,代理是若干个对象实现了一个共同的接口,而委派只是说明一个对象引用了另一个对象,并不牵扯接口。
适配器模式:
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
Spring AOP 模块对 BeforeAdvice、AfterAdvice、ThrowsAdvice 三种通知类型的支持实际上是借助适配器模式来实现的,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,上述三种通知类型是 Spring AOP 模块定义的,它们是 AOP 联盟定义的 Advice 的子类型。
在实际开发环境中,一般都会有登陆的功能,但是由于业务的拓展升级,登陆的方式也会有新的改变,可以有QQ登陆,微信登陆,手机号码登陆等等,那么怎么在不影响原先的业务的情况下去拓展新的业务呢,那么这里就可以用适配器模式来拓展。
先定义一个service模拟线上已定的稳定到不可变的登录注册业务接口及实现:
public interface ISigninService { //注册方法 public ResultMsg regist(String username,String password); // 登录的方法 public ResultMsg login(String username,String password); } public class SiginService implements ISigninService { /** * 注册方法 * @param username * @param password * @return */ public ResultMsg regist(String username, String password){ return new ResultMsg("200","注册成功",new Member()); } /** * 登录的方法 * @param username * @param password * @return */ public ResultMsg login(String username,String password){ return null; } }
其实涉及的 Menber 人员实体类及 ResultMsg 返回信息如下:
public class Member { private String username; private String password; // 唯一编号 private String mid; // 具体信息 private String info; //为节省篇幅,这里删除了set跟get方法 } public class ResultMsg { //返回编码 private String code; // 返回信息 private String msg; //返回数据 private Object data; public ResultMsg(String code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } //为节省篇幅,这里删除了set跟get方法 }
现在业务拓展了,包括 QQ登陆,微信登陆,手机号码登陆,怎么来做适配呢,当然是继承咯,看代码:
public class SiginForThirdService extends SiginService { public ResultMsg loginForQQ(String openId){ //1、openId是全局唯一,我们可以把它当做是一个用户名(加长) //2、密码默认为是一个常量QQ_EMPTY //3、注册(在原有系统里面创建一个用户) //4、调用原来的登录方法 return loginForRegist(openId,null); } public ResultMsg loginForWechat(String openId){ return null; } public ResultMsg loginForTelphone(String telphone,String code){ return null; } // 这里封装了原来的注册跟登陆,想要用一个陌生的QQ来登陆,首先当然是注册 // 注册完调用一下登陆不久OK了吗 public ResultMsg loginForRegist(String username,String password){ super.regist(username,null); return super.login(username,null); } }
这样就完美的做到了业务的兼容。