不兼容结构的协调—适配器模式
我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够 在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了 这个电源适配器,生活用电和笔记本电脑即可兼容
在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样 引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模 式。
适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口 和抽象目标类接口。适配器模式定义如下:
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那 些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可 以作为对象结构型模式。
适配器模式分类:
- 类适配器(通过引用适配者进行 继承 实现)
- 对象适配器(通过 组合 适配者进行实现)
- 接口适配器(通过 抽象类 来实现适配器)
1.类适配器模式
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用B中的合适方法,这样就完成了一个简单的类适配器。
举个栗子:通过把普通登录的接口转换成微信登录的接口
登录返回状态的类
/** * @Project: spring * @description: 登录返回的结果的类 * @author: sunkang * @create: 2018-09-05 20:55 * @ModificationHistory who when What **/ 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; } public String getCode() { return code; } public void setCode(String 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; } } |
普通登录接口:
/** * @Project: spring * @description: 登录的接口业务 * @author: sunkang * @create: 2018-09-05 20:51 * @ModificationHistory who when What **/ public interface ISiginSerevice { ResultMsg login(String username,String password); } |
登录的具体实现:
* @Description: * @Param: 登录的具体实现 * @return: * @Author: sunkang * @Date: 2018/9/5 */ public class SiginService implements ISiginSerevice { /** * 登录的方法 * @param username * @param password * @return */ public ResultMsg login(String username,String password){ return new ResultMsg("200","登录成功",new Object()); } } |
通过微信接口登录:
/** * @Project: spring * @description: 通过微信接口登录 * @author: sunkang * @create: 2018-09-05 20:58 * @ModificationHistory who when What **/ public interface ISiginForWebChat { /** * 通过微信登录 * @param openId * @return */ ResultMsg loginForWechat(String openId); } |
类适配器(把原有的登录方法适配成微信登录的接口)
/** * @Project: spring * @description: 类适配器 目标接口为ISiginForWebChat,但是适配者的接口为ISiginService * @author: sunkang * @create: 2018-09-05 21:29 * @ModificationHistory who when What **/ public class ClassSiginForWebChatAdapter extends SiginService implements ISiginForWebChat{ @Override public ResultMsg loginForWechat(String openId) { return login(openId,null); } } |
2.对象适配器模式:
原理:通过组合来实现适配器功能。
由于比较简单,应该是比较清楚的,代码如下:
/** * @Project: spring * @description: 对象适配器 默认通过组合来实现的 * @author: sunkang * @create: 2018-09-05 21:06 * @ModificationHistory who when What **/ public class ObjectSiginForWebChatAdapter implements ISiginForWebChat { private ISiginSerevice siginSerevice; public ObjectSiginForWebChatAdapter(ISiginSerevice siginSerevice) { this.siginSerevice = siginSerevice; } @Override public ResultMsg loginForWechat(String openId) { return siginSerevice.login(openId,null); } } |
3.接口适配器模式:也叫缺省适配器
原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法
,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
比如现在登录要支持第三方接口,比如通过qq登录,通过微信登录,通过手机号和验证码登录,但是有的实现类不是通通用到这些方法,可以用一个抽象类来实现接口的所有的方法,作为一个默认的实现,然后具体的实现类继承抽>象类,要用到哪个方法就重写哪个方法
第三方登录的接口:
/** * @Project: spring * @description: 第三方登录的接口 * @author: sunkang * @create: 2018-09-05 21:41 * @ModificationHistory who when What **/ public interface ISiginForThirdService { /** * 通过qq登录 * @param openId * @return */ ResultMsg loginForQQ(String openId); /** * 通过微信登录 * @param openId * @return */ ResultMsg loginForWechat(String openId); /** * 通过手机号和验证码登录 * @param telphone * @param code * @return */ ResultMsg loginForTelphone(String telphone,String code); } |
抽象适配器:
/** * @Project: spring * @description: 抽象适配器 因为接口太多,所以用了一个抽象的类的适配器,来默认实现 * @author: sunkang * @create: 2018-09-05 21:43 * @ModificationHistory who when What **/ public abstract class SiginForThirdAdapter implements ISiginForThirdService { @Override public ResultMsg loginForQQ(String openId) { return new ResultMsg("200","qq登录成功",new Object()); } @Override public ResultMsg loginForWechat(String openId) { return new ResultMsg("200","微信登录成功",new Object()); } @Override public ResultMsg loginForTelphone(String telphone, String code) { return new ResultMsg("200","手机登录成功",new Object()); } } |
通过电话号码和动态密码登录,只需要重写对应的方法接口
/** * @Project: spring * @description: 只需要用电话号码登录 * @author: sunkang * @create: 2018-09-05 21:44 * @ModificationHistory who when What **/ public class LoginForTelphoneAdapter extends SiginForThirdAdapter { public ResultMsg loginForTelphone(String telphone, String code) { System.out.println("通过电话号码登录"); ResultMsg msg = new ResultMsg("200","qq登录成功",new Object()); System.out.println("登录的结果为"+msg.getMsg()); return msg; } } |
三种方式的测试案例
/** * @Project: spring * @description: 三种方式实现的测试案例 * @author: sunkang * @create: 2018-09-05 21:17 * @ModificationHistory who when What **/ public class AdapterTest { public static void main(String[] args) { //需要适配的类,要把 ISiginSerevice 变成 ISiginForWebChat ISiginSerevice siginSerevice = new SiginService(); ISiginForWebChat siginForThreadService = new ObjectSiginForWebChatAdapter(siginSerevice); //对象适配器 ,传的是一个适配的类,通过组合的方式 ResultMsg resultMsg = siginForThreadService.loginForWechat("1231321ffasfasffad"); System.out.println(resultMsg.getMsg()); //类适配器,通过继承实现的 ResultMsg msg = new ClassSiginForWebChatAdapter().loginForWechat("1231321ffasfasffad"); System.out.println(msg.getMsg()); //接口适配器 ,主要实现了一个抽象的适配器 //但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口 //并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器 ISiginForThirdService siginForWebChat = new LoginForTelphoneAdapter(); ResultMsg telPoneMsg = siginForWebChat.loginForTelphone("13588304966","1234"); System.out.println(telPoneMsg.getMsg()); } } |
适配器模式总结
适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用 频率非常高的设计模式,在软件开发中得以广泛应用,在Spring等开源框架、驱动程序设计 (如JDBC中的数据库驱动程序)中也使用了适配器模式。
1. 主要优点
无论是对象适配器模式还是类适配器模式都具有如下优点:
(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有 结构。
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而 言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3) 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修 改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体来说,类适配器模式还有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配 器的灵活性更强。
对象适配器模式还有如下优点:
(1) 一个对象适配器可以把多个不同的适配者适配到同一个目标;
(2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原 则”,适配者的子类也可通过该适配器进行适配。
1. 主要缺点
类适配器模式的缺点如下:
(1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适 配多个适配者;
(2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
(3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一 定的局限性。
对象适配器模式的缺点如下:
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉 适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然 后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
1. 适用场景
在以下情况下可以考虑使用适配器模式:
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有 这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可 能在将来引进的类一起工作。