1. 生活中的适配器

image.png

2. 适配器模式概念

适配器模式(Adapter Pattern)是指将一个类的接口转化成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。

3. 适配器模式的适用场景

  1. 已经存在的类,它的方法与需求不匹配(方法结果相同或者相似)的情况
  2. 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护由于不同产品、不同厂家造成功能类似而接口不同情况下的解决方案。
  3. 案例展示:(直流AC与交流DC转换)

AC220直流220v类

  1. public class AC220 {
  2. public int outputAcc220V(){
  3. int output = 220 ;
  4. System.out.println("输出电流"+ output + "V");
  5. return output;
  6. }
  7. }

直流电的输入类

DC5 交流5接口

  1. public interface DC5 {
  2. int outputDC5V();
  3. }

交流电的输出接口

PowerAdapter电流转换类

  1. public class PowerAdapter implements DC5{
  2. private AC220 ac220;
  3. public PowerAdapter(AC220 ac220){
  4. this.ac220 = ac220;
  5. }
  6. @Override
  7. public int outputDC5V() {
  8. int adapterInput = ac220.outputAcc220V();
  9. int adapterOutput = adapterInput / 44;
  10. System.out.println("使用PowerAdapter输入AC:" + adapterInput + "V,输出DC:" + adapterOutput + "V");
  11. return adapterOutput;
  12. }
  13. }

通过将传入的直流电转化为可用的交流电输出

测试类

  1. public class PowerAdapterTest {
  2. public static void main(String[] args) {
  3. DC5 dc5 = new PowerAdapter(new AC220());
  4. dc5.outputDC5V();
  5. }
  6. }

image.png

4. 登录场景的适配器

登录的流程无非就是输入用户名、密码和验证码然后验证成功登录,但是不同的业务处理登录的逻辑会有所不同,比如说现在流行的qq登录、支付宝登录、微信登录等等。

SiginService 登录service

  1. public class SiginService {
  2. /**
  3. * 注册方法
  4. * @param username
  5. * @param password
  6. * @return
  7. */
  8. public ResultMsg regist(String username, String password){
  9. return new ResultMsg(200,"注册成功",new Member());
  10. }
  11. /**
  12. * 登录的方法
  13. * @param username
  14. * @param password
  15. * @return
  16. */
  17. public ResultMsg login(String username,String password){
  18. return null;
  19. }
  20. }

SigninForThirdService第三方登录适配

  1. public class SigninForThirdService extends SiginService{
  2. public ResultMsg loginForQQ(String openId){
  3. //1、openId是全局唯一,我们可以把它当做是一个用户名(加长)
  4. //2、密码默认为QQ_EMPTY
  5. //3、注册(在原有系统里面创建一个用户)
  6. //4、调用原来的登录方法
  7. return loginForRegist(openId,null);
  8. }
  9. public ResultMsg loginForWechat(String openId){
  10. return null;
  11. }
  12. public ResultMsg loginForToken(String token){
  13. //通过token拿到用户信息,然后再重新登陆了一次
  14. return null;
  15. }
  16. public ResultMsg loginForTelphone(String telphone,String code){
  17. return null;
  18. }
  19. public ResultMsg loginForRegist(String username,String password){
  20. super.regist(username,null);
  21. return super.login(username,null);
  22. }
  23. }

Member类

  1. ublic class Member {
  2. private String username;
  3. private String password;
  4. private String mid;
  5. private String info;
  6. public String getUsername() {
  7. return username;
  8. }
  9. public void setUsername(String username) {
  10. this.username = username;
  11. }
  12. public String getPassword() {
  13. return password;
  14. }
  15. public void setPassword(String password) {
  16. this.password = password;
  17. }
  18. public String getMid() {
  19. return mid;
  20. }
  21. public void setMid(String mid) {
  22. this.mid = mid;
  23. }
  24. public String getInfo() {
  25. return info;
  26. }
  27. public void setInfo(String info) {
  28. this.info = info;
  29. }
  30. }

ResultMsg类

  1. public class ResultMsg {
  2. private int code;
  3. private String msg;
  4. private Object data;
  5. public ResultMsg(int code, String msg, Object data) {
  6. this.code = code;
  7. this.msg = msg;
  8. this.data = data;
  9. }
  10. public int getCode() {
  11. return code;
  12. }
  13. public void setCode(int code) {
  14. this.code = code;
  15. }
  16. public String getMsg() {
  17. return msg;
  18. }
  19. public void setMsg(String msg) {
  20. this.msg = msg;
  21. }
  22. public Object getData() {
  23. return data;
  24. }
  25. public void setData(Object data) {
  26. this.data = data;
  27. }
  28. }

这两个类不需要解释太多,通过查看第三方登录逻辑可以发现,登录的核心逻辑依然是login方式,但是为了适配不同的应用场景,选择了调用了不同的适配方法达到了需求。

5. 登录场景再升级

IPassportForThird第三方登录接口只扩展

  1. public interface IPassportForThird {
  2. /**
  3. * QQ登录
  4. * @param id
  5. * @return
  6. */
  7. ResultMsg loginForQQ(String id);
  8. /**
  9. * 微信登录
  10. * @param id
  11. * @return
  12. */
  13. ResultMsg loginForWechat(String id);
  14. /**
  15. * 记住登录状态后自动登录
  16. * @param token
  17. * @return
  18. */
  19. ResultMsg loginForToken(String token);
  20. /**
  21. * 手机号登录
  22. * @param telphone
  23. * @param code
  24. * @return
  25. */
  26. ResultMsg loginForTelphone(String telphone, String code);
  27. /**
  28. * 注册后自动登录
  29. * @param username
  30. * @param passport
  31. * @return
  32. */
  33. ResultMsg loginForRegist(String username, String passport);
  34. }

PassportForThirdAdapter第三方接口实现类

  1. public class PassportForThirdAdapter extends SiginService implements IPassportForThird{
  2. @Override
  3. public ResultMsg loginForQQ(String id) {
  4. return processLogin(id,LoginForQQAdapter.class);
  5. }
  6. @Override
  7. public ResultMsg loginForWechat(String id) {
  8. return processLogin(id, LoginForWechatAdapter.class);
  9. }
  10. @Override
  11. public ResultMsg loginForToken(String token) {
  12. return processLogin(token, LoginForTokenAdapter.class);
  13. }
  14. @Override
  15. public ResultMsg loginForTelphone(String telphone, String code) {
  16. return processLogin(telphone, LoginForTelAdapter.class);
  17. }
  18. @Override
  19. public ResultMsg loginForRegist(String username, String passport) {
  20. super.regist(username,passport);
  21. return super.login(username,passport);
  22. }
  23. /**
  24. * 简单工厂模式
  25. * @param key
  26. * @param clazz
  27. * @return
  28. */
  29. private ResultMsg processLogin(String key,Class<? extends LoginAdapter> clazz){
  30. try {
  31. //适配器不一定要实现接口
  32. LoginAdapter adapter = clazz.newInstance();
  33. if (adapter.support(adapter)){
  34. return adapter.login(key,adapter);
  35. }
  36. }catch (Exception e){
  37. e.printStackTrace();
  38. }
  39. return null;
  40. }
  41. }

这个类作为第三方接口实现类,重写了所有接口。这里最后提供了一个使用简单工厂实现的方法,用于所有第三方验证的接口调用适配,对所传入的参数类型进行判断,属于哪一种类型就会自动调用该类型接口,达到了自动识别的效果。

LoginAdapter 登录适配器接口

  1. /**
  2. * 在适配器中这个接口是可有可无的 不要跟模板模式混淆
  3. * 模板模式一定是抽象类 这里仅仅是一个接口
  4. * @author hkfire
  5. * @create 2021-07-12 14:40
  6. * @description
  7. */
  8. public interface LoginAdapter {
  9. boolean support(Object adapter);
  10. ResultMsg login(String id,Object a);
  11. }

这个登录适配器接口用于后续每一种情况的登录验证,主要包含两个方法,一个是判断是否支持当前适配器类型,还有个方法就是登录方法。

LoginForQQAdapter第三方QQ登录适配器

  1. public class LoginForQQAdapter implements LoginAdapter{
  2. @Override
  3. public boolean support(Object adapter) {
  4. return adapter instanceof LoginForQQAdapter;
  5. }
  6. @Override
  7. public ResultMsg login(String id, Object a) {
  8. return null;
  9. }
  10. }

LoginForSinaAdapter第三方新浪登录适配器

  1. public class LoginForSinaAdapter implements LoginAdapter{
  2. @Override
  3. public boolean support(Object adapter) {
  4. return adapter instanceof LoginForSinaAdapter;
  5. }
  6. @Override
  7. public ResultMsg login(String id, Object a) {
  8. return null;
  9. }
  10. }

LoginForTelAdapter第三方手机登录适配器

  1. public class LoginForTelAdapter implements LoginAdapter{
  2. @Override
  3. public boolean support(Object adapter) {
  4. return adapter instanceof LoginForTelAdapter;
  5. }
  6. @Override
  7. public ResultMsg login(String id, Object a) {
  8. return null;
  9. }
  10. }

LoginForTokenAdapter第三方token登录适配器

  1. public class LoginForTokenAdapter implements LoginAdapter {
  2. @Override
  3. public boolean support(Object adapter) {
  4. return adapter instanceof LoginForTokenAdapter;
  5. }
  6. @Override
  7. public ResultMsg login(String id, Object a) {
  8. return null;
  9. }
  10. }

LoginForWechatAdapter第三方微信登录适配器

  1. public class LoginForWechatAdapter implements LoginAdapter{
  2. @Override
  3. public boolean support(Object adapter) {
  4. return adapter instanceof LoginForWechatAdapter;
  5. }
  6. @Override
  7. public ResultMsg login(String id, Object a) {
  8. return null;
  9. }
  10. }

这样我们整个登录流程的适配器案例就算是写完了,那么回头梳理一下整个调用思路,首先想好你登录的场景,qq或者是微信,那么需要new一个PassportForThirdAdapter出来,用IPassportForThird接收,然后调用你的登录方式方法,传入匹配的类型,例如调用的这个processLogin(id,LoginForQQAdapter.class),之后会根据你传入的参数类型去判断选择哪一个适配器类型,这里选择的是qq登录,然后去验证是否支持qq登录,会去调用该适配器的support方法,返回true则适用,false则不适用,验证成功了,再去调用登录方法,进行参数值验证。

适配器模式在我们spring的aop中使用的比较常见,aop中有个AdvisorAdapter通知类,可以根据不同的配置达到不同通知的效果,感兴趣的小伙伴可以翻翻源码。

6. 适配器模式的缺点

  1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性。
  2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。