学习目标:通过学习适配器模式,优雅地解决代码功能的兼容性问题。
适配器模式的定义:
- 适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作。
- 属于结构型模式。
适配器模式的适用场景:
- 已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。
- 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。
适配器源码案例:
- SpringAOP源码中的AdvisorAdapter接口
它的具体实现有AfterReturningAdviceAdapter(方法返回时通知)、MethodBeforeAdviceAdapter(方法调用前通知)、ThrowsAdviceAdapter(方法出现异常通知),spring根据配置需要调用需要的通知,也可以时三个,也可以是两个;而策略模式只能选择一种逻辑,不能并行
类结构图
- SpringMvc的HandlerAdapter接口
类结构图
生活场景案例
案例:变压器将家用220v交流电转换成5v直流电
5v直流电接口
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:39 * @description: 5V直流电 */ public interface DC5 { int outputDC5V(); }
220v交流电
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:37 * @description: 220V交流电 */ public class AC220 { public int outputAC220V(){ int output = 220; System.out.println("输出电流" + output + "V"); return output; } }
变压器
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:41 * @description: */ public class PowerAdapter implements DC5 { private AC220 ac220; public PowerAdapter(AC220 ac220) { this.ac220 = ac220; } @Override public int outputDC5V() { int adapterInput = ac220.outputAC220V(); int adapterOutput = adapterInput / 44; System.out.println("使用PowerAdapter输入AC:" + adapterInput + "V,输出DC:" + adapterOutput + "V"); return adapterOutput; } }
测试
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:45 * @description: */ public class PowerAdapterTest { public static void main(String[] args) { DC5 dc5 = new PowerAdapter(new AC220()); dc5.outputDC5V(); } }
业务场景案例
案例:系统需要在原有的登录方式上,添加QQ、微信等多种登录方式
返回对象ResultMsg
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:55 * @description: */ public class ResultMsg { private int code; private String msg; private String data; public ResultMsg(int code, String msg, String data) { this.code = code; this.msg = msg; this.data = data; } }
已经存在的登录注册逻辑
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:51 * @description: 原有的登录注册逻辑 */ public class SinginService { /** * 注册方法 */ public ResultMsg regist(String username,String password){ return new ResultMsg(200,"注册成功",null); } /** * 登录方法 */ public ResultMsg login(String username,String password){ return new ResultMsg(200,"登录成功",null); } }
方式一:直接继承原有登录注册服务
SinginForThirdService.java
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:58 * @description: */ public class SinginForThirdService extends SinginService { 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 loginForToken(String token){ // 通过token拿到用户信息,然后再重新登录了一次 return null; } public ResultMsg loginForTelphone(String telphone,String code){ return null; } /** * qq登录自动注册 */ public ResultMsg loginForRegist(String username,String password){ super.regist(username,null); return super.login(username,null); } }
测试
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:11 * @description: */ public class SinginForThirdServiceTest { public static void main(String[] args) { SinginForThirdService singinForThirdService = new SinginForThirdService(); singinForThirdService.login("zc","123456"); singinForThirdService.loginForQQ("fadfafd"); singinForThirdService.loginForWechat("fdsaf"); } }
方式二:利用适配器模式
Spring中适配器的实现方式,完全模仿SpringAOP的AdivsorAdapter、Spring Web Mvc中的HandlerAdapter实现
业务扩展接口
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:13 * @description: 只扩展的接口 */ public interface IPassportForThird { /** * QQ登录 */ ResultMsg loginForQQ(String openId); /** * 微信登录 */ ResultMsg loginForWechat(String openId); /** * 新浪登录 */ ResultMsg loginForSina(String openId); /** * token登录 */ ResultMsg loginForToken(String token); /** * 手机验证登录 */ ResultMsg loginForTelphone(String telphone,String code); /** * 注册账号后自动登录 */ ResultMsg loginForRegister(String username,String passport); }
适配器接口(可有可无)
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:20 * @description: 在适配器里面这个接口是可无可有的,不要和模板模式混淆, * 模板模式一定是抽象类,而这里面只是一个接口 */ public interface LoginAdapter { /** * 适配器兼容性判断 */ boolean support(Object adapter); /** * 登录逻辑 */ ResultMsg login(String id,Object a); }
QQ登录
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:23 * @description: QQ登录 */ public class LoginForQQAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForQQAdapter; } @Override public ResultMsg login(String id, Object a) { // 具体逻辑 return null; } }
新浪登录
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:26 * @description: 新浪登录 */ public class LoginForSinaAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForSinaAdapter; } @Override public ResultMsg login(String id, Object a) { return null; } }
手机短信登录
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:28 * @description: 手机短信登录 */ public class LoginForTelAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForTelAdapter; } @Override public ResultMsg login(String id, Object a) { return null; } }
token令牌登录
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:29 * @description: token登录 */ public class LoginForTokenAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForTokenAdapter; } @Override public ResultMsg login(String id, Object a) { return null; } }
微信登录
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:30 * @description: 微信登录 */ public class LoginForWechatAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForWechatAdapter; } @Override public ResultMsg login(String id, Object a) { return null; } }
登录适配器
/** * @author: ZhouCong * @date: Create in 2021/1/12 12:18 * @description: 继承原有业务实现扩展接口 结合策略模式、工厂模式、适配器模式 */ public class PassportForThirdAdapter extends SinginService implements IPassportForThird { @Override public ResultMsg loginForQQ(String openId) { /* // 适配器不一定要接口 LoginAdapter adapter = new LoginForQQAdapter(); if (adapter.support(adapter)){ return adapter.login(openId,adapter); } return null; // 用工厂模式简化代码 */ // 策略模式体现,用户选择不同的逻辑 return processLogin(openId, LoginForQQAdapter.class); } @Override public ResultMsg loginForWechat(String openId) { /* // 适配器不一定要接口 LoginAdapter adapter = new LoginForWechatAdapter(); if (adapter.support(adapter)){ return adapter.login(openId,adapter); } return null;*/ // 策略模式体现,用户选择不同的逻辑 return processLogin(openId, LoginForWechatAdapter.class); } @Override public ResultMsg loginForSina(String openId) { // 策略模式体现,用户选择不同的逻辑 return processLogin(openId, LoginForSinaAdapter.class); } @Override public ResultMsg loginForToken(String token) { // 策略模式体现,用户选择不同的逻辑 return processLogin(token, LoginForTokenAdapter.class); } @Override public ResultMsg loginForTelphone(String telphone, String code) { // 策略模式体现,用户选择不同的逻辑 return processLogin(telphone, LoginForTelAdapter.class); } @Override public ResultMsg loginForRegister(String username, String passport) { super.regist(username, passport); return super.login(username, passport); } /** * 简单工厂模式使用,简化代码,不用每一个登录方式都要写一遍判断并实例化对应的对象 */ private ResultMsg processLogin(String key, Class<? extends LoginAdapter> clazz) { try { // 适配器不一定要接口 LoginAdapter adapter = clazz.newInstance();
// 判断传过来的适配器是否能处理指定的逻辑 if (adapter.support(adapter)) { return adapter.login(key, adapter); } else { return null; } } catch (Exception e) { e.printStackTrace(); } return null; } }
测试用例
/** * @author: ZhouCong * @date: Create in 2021/1/12 14:48 * @description: */ public class PassportTest { public static void main(String[] args) { PassportForThirdAdapter passportForThirdAdapter = new PassportForThirdAdapter(); passportForThirdAdapter.loginForQQ("fdsfa"); } }
附上类结构图(查看类图快捷键Ctrl+Alt+Shift+U)
总结
适配器模式的优点:
- 能提高类的透明性和复用,现有的类复用但不需要改变。
- 目标类和适配器类解耦,提高程序的可扩展性。
- 在很多业务场景中符合开闭原则。
适配器模式的缺点:
- 适配器编写过程需要全面考虑,可能会增加系统的复杂性。
- 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。可能需要了解系统的整个架构才能看懂。
以上对适配器模式的介绍到此结束,欢迎批评指正。 附:源码地址