类图
适配器模式的应用场景:
适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。
适配器适用于以下几种业务场景:
1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。
2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。
生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。
适配器模式入门小demo
适配的接口
public interface DC5 { int outputDC5V(); }
被适配的接口/类
public class AC220 { public int outputAC220V() { int output = 220; System.out.println("输出交流电: " + output + "v"); return output; } }
适配类:
public class PowerAdapter implements DC5 { private AC220 ac220; public PowerAdapter(AC220 ac220) { this.ac220 = ac220; } @Override public int outputDC5V() { int adapterInput = ac220.outputAC220V(); int adapetOutput = adapterInput / 44; System.out.println("使用PowerAdapter 输入AC: " + adapetOutput + "V " + "输出DC:" + adapetOutput + "V"); return adapetOutput; } }
测试
public class OjectAdapterTest { public static void main(String[] args) { DC5 dc5 = new PowerAdapter(new AC220()); dc5.outputDC5V(); } }
重构第三登录自由适配的业务场景
下面我们来一个实际的业务场景,利用适配模式来解决实际问题。年纪稍微大一点的小伙伴一定经历过这样一个过程。我们很早以前开发的老系统应该都有登录接口,但是随着业务的发展和社会的进步,单纯地依赖用户名密码登录显然不能满足用户需求了。现在,我们大部分系统都已经支持多种登录方式,如QQ 登录、微信登录、手机登录、微博登录等等,同时保留用户名密码的登录方式。虽然登录形式丰富了,但是登录后的处理逻辑可以不必改,同样是将登录状态保存到session,遵循开闭原则。
简单的写法:
/** * Title: ResultMsg * Description: 统一返回格式 * * @author hfl * @version V1.0 * @date 2020-05-21 */ 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; } }
登录参数:
public class Member { 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; } }
/** * 最简单的 注册登录方式(被适配类Adaptee) */ public class SiginService { /** * 注册方法 * * @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; } }
/** * 新增第三方登录方式, * 兼容原来的注册登录(通过继承方式实现) */ public class SinginForThirdService 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 loginForToken(String token){ //通过token拿到用户信息,然后再重新登陆了一次 return null; } public ResultMsg loginForTelphone(String telphone,String code){ return null; } public ResultMsg loginForRegist(String username,String password){ super.regist(username,null); return super.login(username,null); } }
测试: 原来的用户名和密码登录 和现在新增的 通过qq和微信的方式都可以使用到。
public class SiginForThirdServiceTest { public static void main(String[] args) { SinginForThirdService service = new SinginForThirdService(); service.login("tom","123456"); service.loginForQQ("sdfasdfasf"); service.loginForWechat("sdfasfsa"); } }
优雅的写法
上面的写法可以满足适配,但是,spring源码中采用更优雅的方式,我们也可以模仿下spring源码的方式,反射生成适配类 来实现这个需求:
public class PassportForThirdAdapter extends SiginService implements IPassportForThird { @Override public ResultMsg loginForQQ(String id) { return processLogin(id, LoginForQQAdapter.class); } @Override public ResultMsg loginForWechat(String id) { return processLogin(id, LoginForWechatAdapter.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 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; } }
* Title: IPassportForThird * Description: 第三方登录兼容接口 * * @author hfl * @version V1.0 * @date 2020-05-23 */ public interface IPassportForThird { /** * 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); }
public interface LoginAdapter { boolean support(Object adapter); ResultMsg login(String id, Object adapter); }
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; } }
/** * 新浪微博登录 */ 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; } }
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; } }
/** * Title: LoginForTokenAdapter * Description: token自动登录 * * @author hfl * @version V1.0 * @date 2020-05-23 */ 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; } }
/** * Title: LoginForWechatAdapter * Description: 微信登录 * * @author hfl * @version V1.0 * @date 2020-05-23 */ 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; } }
public class PassportTest { public static void main(String[] args) { IPassportForThird passportForThird = new PassportForThirdAdapter(); //原来的接口 可用 passportForThird.loginForRegist("admin","admin"); //新增适配的接口 同样可用 passportForThird.loginForQQ("11"); } }
至此,我们在遵循开闭原则的前提下,完整地实现了一个兼容多平台登录的业务场景。
当然,我目前的这个设计也并不完美,仅供参考,感兴趣的小伙伴可以继续完善这段代
码。例如适配器中的参数目前是写死为String,改为Object[]应该更合理。
适配器的3种方式
1.类适配器
2.对象适配器器
3.接口适配器
之前的一篇适配的文章有提到。
适配器模式的本质
什么时候使用适配器模式
相关模式