乐趣区

关于spring:设计模式适配器模式

适配器模式

智者千虑必有一失,愚者千虑必有一得

在咱们做设计的时候总是会呈现一些 意外,适配器模式就是帮咱们来补救这些意外的

定义

变压模式,也叫包装模式, 然而包装模式可不止一个。装璜者也是。

性能是将 一个类的接口编程客户端所冀望的另一个接口,从而使本来接口不匹配而无奈在一起工作的两个类可能一起工作

属于结构型设计模式

例子

Switch 的港版电压在国内是不实用的,须要两脚转三角插头

有点亡羊补牢的感觉

通用写法

类适配器

Target 指标角色

​ 该角色定义把其余类转换为何种接口,也就是咱们的冀望接口,例 子中的 IUserInfo 接口就是指标角色。

public interface Target {int request();
}

​ 指标角色是一个曾经在正式运行的角色,你不可能去批改角色中的办法,你能做的就是如何去实现接口中的办法,而且通常状况下,指标角色是一个接口或者是抽象类,个别不会是实现类。

Adaptee 源角色

​ 你想把谁转换成指标角色,这个“谁”就是源角色,它是曾经存在的、运行良好的类或对象,通过适配器角色的包装,它会成为一个簇新、靓丽的角色。

public class Adaptee{public int specificRequest() {return 220;}
}
Adapter 适配器角色

​ 适配器模式的外围角色,其余两个角色都是曾经存在的角色,而适配器角色是须要新建设的,它的职责非常简单:把源角色转换为指标角色,怎么转换?

罕用的三种形式
1. 通过继承源角色

public class Adapter extends Adaptee implements Target {public int request() {return super.specificRequest() / 10;
    }
}
2. 通过组合源角色

public class Adapter implements Target {
  private Adaptee adaptee;
  public Adapter(Adaptee adaptee){this.adaptee = adaptee;}
  public int request() {return adaptee.specificRequest() / 10;
  }
}
3. 接口适配,实现交给客户端
public abstract class Adapter implements Target {

  protected Adaptee adaptee;
  public Adapter(Adaptee adaptee){this.adaptee = adaptee;}

  public int request1() {return 0;}

  public int request2() {return 0;}

  public int request3() {return 0;}

  public int request4() {return 0;}
}
public class Test {public static void main(String[] args) {Target adapter = new Adapter(new Adaptee()) {
            @Override
            public int request1() {return adaptee.specificRequest() / 10;
            }
        };
        int result = adapter.request1();
        System.out.println(result);
    }
}

对象适配器

合乎开闭准则

相似于装璜者模式

接口适配器

解决接口办法过多,有很多空的办法

违反接口隔离和繁多职责

案例

电压的转化

基于继承
public interface DC5 {int output5V();
}
public class AC220 {public int outputAC220V(){
    int output = 220;
    System.out.println("输入电压" + 220 + "V");
    return output;
  }
}
public class PowerAdapter extends AC220 implements DC5 {public int output5V() {int adapterInput = super.outputAC220V();
    // 具体转换就在这里只有这里能够转换就是适配器,至于继承 AC220 和实现 DC5 是 java 语法下面的写法,经验总结这些写法是比拟好的 <br>
    int adapterOutput = adapterInput / 44;
    System.out.println("应用 Adapter 输出 AC" + adapterInput + "V, 输入 DC" + adapterOutput + "V");
    return adapterOutput;
  }
}
public static void main(String[] args) {
  // 输入 DC5<br>
  DC5 adapter = new PowerAdapter();
  adapter.output5V();
  //
  //PowerAdapter 还能够输入 220 违反了起码晓得准则
}

能够看到,基于继承的形式,违反了起码晓得准则和合成复用准则

基于组合
public class PowerAdapter implements DC5 {
  private AC220 ac220;

  public PowerAdapter(AC220 ac220) {this.ac220 = ac220;}

  public int output5V() {int adapterInput = ac220.outputAC220V();
    int adapterOutput = adapterInput / 44;
    System.out.println("应用 Adapter 输出 AC" + adapterInput + "V, 输入 DC" + adapterOutput + "V");
    return adapterOutput;
  }
}
public class Test {public static void main(String[] args) {
        // 应用组合来实现
        DC5 adapter = new PowerAdapter(new AC220());
        adapter.output5V();

        // 对于内部只具备他应该晓得的性能,比方 DC5<br>
    }
}

这种形式是比拟好的,合乎起码晓得准则

注册逻辑

咱们的老零碎都应该有登录接口,随着业务的倒退单纯的依赖用户名和明码曾经不能满足要求了。

当初有很多登录形式

  • qq 登录
  • 微信登录
  • 手机登录等

当初咱们须要适配老零碎的接口

  • 注册逻辑固定

    • 咱们须要适配 qq
    • 微信
    • 手机号等
public class PassportService {

  /**
     * 注册办法
     * @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 interface IPassportForThird {ResultMsg loginForQQ(String openId);

  ResultMsg loginForWechat(String openId);

  ResultMsg loginForToken(String token);

  ResultMsg loginForTelphone(String phone, String code);

}
通过继承的形式实现适配
public class PassportForThirdAdapter extends PassportService implements IPassportForThird {public ResultMsg loginForQQ(String openId) {return loginForRegist(openId,null);
  }

  public ResultMsg loginForWechat(String openId) {return loginForRegist(openId,null);
  }

  public ResultMsg loginForToken(String token) {return loginForRegist(token,null);
  }

  public ResultMsg loginForTelphone(String phone, String code) {return loginForRegist(phone,null);
  }

  private ResultMsg loginForRegist(String username,String password){if(null == password){password = "THIRD_EMPTY";}
    super.regist(username,password);
    return super.login(username,password);
  }
}

这种形式,当前一旦须要新增登录形式,那么咱们适配器就须要批改,违反开闭准则,并且咱们具体的登录逻辑都须要写在这个外面,代码会十分的长

通过组合的形式实现适配

咱们能够模仿 Spring 的,通过遍从来判断是否合乎,如果合乎就调用对应的适配器来适配

public interface ILoginAdapter {boolean support(Object object);
    ResultMsg login(String id, Object adapter);
}

适配层

public class PassportForThirdAdapter implements IPassportForThird {public ResultMsg loginForQQ(String openId) {return processLogin(openId, LoginForQQAdapter.class);
    }

    public ResultMsg loginForWechat(String openId) {return processLogin(openId, LoginForWechatAdapter.class);

    }

    public ResultMsg loginForToken(String token) {return processLogin(token, LoginForTokenAdapter.class);
    }

    public ResultMsg loginForTelphone(String phone, String code) {return processLogin(phone, LoginForTelAdapter.class);
    }

    /**
     * 不想把 LoginForTelAdapter/LoginForTokenAdapter 逻辑都写在一个类里所以辨别开写 <br>
     * @param id
     * @param clazz
     * @return
     */
    private ResultMsg processLogin(String id,Class<? extends ILoginAdapter> clazz){
        try {ILoginAdapter adapter = clazz.newInstance();// 这个 new 进去的,我能够从池子里拿到 <br>
            if (adapter.support(adapter)){return adapter.login(id,adapter);
            }
        } catch (Exception e) {e.printStackTrace();
        }
        return null;
    }

}

咱们把对应的登录适配器放在这外面用来遍历适配器 是否匹配,如果匹配就调用具体的适配实现来登录。

public abstract class AbstraceAdapter extends PassportService implements ILoginAdapter {protected ResultMsg loginForRegist(String username, String password){if(null == password){password = "THIRD_EMPTY";}
        super.regist(username,password);
        return super.login(username,password);
    }
}

登录逻辑是固定的,所以提供公共的形象

public class LoginForWechatAdapter extends AbstraceAdapter{public boolean support(Object adapter) {return adapter instanceof LoginForWechatAdapter;}

  public ResultMsg login(String id, Object adapter) {return super.loginForRegist(id,null);
  }
}

QQ 适配同理

public class LoginForQQAdapter extends AbstraceAdapter{public boolean support(Object adapter) {return adapter instanceof LoginForQQAdapter;}

    public ResultMsg login(String id, Object adapter) {if(!support(adapter)){return null;}
        //accesseToken  理论工作场景中你是要写很多代码的 <br>
        //time
        return super.loginForRegist(id,null);

    }

}

具体类图

源码中的实现

Spring 中的WebMvcConfigurerAdapter

  • 帮忙咱们做了接口的适配,这样实现类就能够不必都实现这些接口,只须要本人关怀的接口了
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {public WebMvcConfigurerAdapter() { }

    public void configurePathMatch(PathMatchConfigurer configurer) { }

    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { }

    public void configureAsyncSupport(AsyncSupportConfigurer configurer) { }

    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { }
  // 胜率
}

在 JAva8 外面曾经反对 Default 来解决这个问题了

public interface WebMvcConfigurer {default void configurePathMatch(PathMatchConfigurer configurer) { }

  default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}}

default 办法咱们接口的实现类就能够不必实现了

SpringAOP

org.springframework.aop.framework.adapter.AdvisorAdapter

public interface AdvisorAdapter {boolean supportsAdvice(Advice advice);
    MethodInterceptor getInterceptor(Advisor advisor);
}

上述是 SpringAOP 的告诉适配器

DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice

这是用来构建告诉链的办法

MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {// Creating a new object instance in the getInterceptors() method
  // isn't a problem as we normally cache created chains.
  for (MethodInterceptor interceptor : interceptors) {interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
  }
}
else {interceptorList.addAll(Arrays.asList(interceptors));
}

上述是通过 AdvisorAdapterRegistry 这个告诉注册器来找到对应的拦截器

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {List<MethodInterceptor> interceptors = new ArrayList<>(3);
  Advice advice = advisor.getAdvice();
  if (advice instanceof MethodInterceptor) {interceptors.add((MethodInterceptor) advice);
  }
  for (AdvisorAdapter adapter : this.adapters) {if (adapter.supportsAdvice(advice)) {interceptors.add(adapter.getInterceptor(advisor));
    }
  }
  if (interceptors.isEmpty()) {throw new UnknownAdviceTypeException(advisor.getAdvice());
  }
  return interceptors.toArray(new MethodInterceptor[0]);
}

通过判断 AdvisorAdapter 是否匹配,如果匹配就退出到告诉链路外面

前置告诉适配器
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

  @Override
  public boolean supportsAdvice(Advice advice) {return (advice instanceof MethodBeforeAdvice);
  }

  @Override
  public MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
    return new MethodBeforeAdviceInterceptor(advice);
  }

}

前置告诉适配器

后置告诉适配器
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {return (advice instanceof AfterReturningAdvice);
    }

    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
        return new AfterReturningAdviceInterceptor(advice);
    }

}
异样告诉适配器
class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {

  @Override
  public boolean supportsAdvice(Advice advice) {return (advice instanceof ThrowsAdvice);
  }

  @Override
  public MethodInterceptor getInterceptor(Advisor advisor) {return new ThrowsAdviceInterceptor(advisor.getAdvice());
  }

}

HandlerAdapter

org.springframework.web.servlet.HandlerAdapter

org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

能够看到获取对应的适配器的中央

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}
    }
  }
  throw new ServletException("No adapter for handler [" + handler +
                             "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

SimpleControllerHandlerAdapter

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

   @Override
   public boolean supports(Object handler) {return (handler instanceof Controller);
   }

   @Override
   @Nullable
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {return ((Controller) handler).handleRequest(request, response);
   }

   @Override
   public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);
      }
      return -1L;
   }

}

能够看到如果是 Controler 的子类,就会应用他来帮咱们来解决申请,适配器用来帮咱们适配了不同的 Handler 解决申请的问题

说是适配,其实有点像委派模式

总结

实用场景

  • 曾经存在的类,他的办法和需要不匹配(办法后果雷同或者类似)的状况
  • 设施器模式不是软件设计阶段思考的设计模式,是随着软件维护,因为不同产品不同厂家造成性能相似而接口不雷同状况下的解决方案

优缺点

长处

  • 能让两个没有任何关系的类在一起运行,只须要适配器这个角色就行了
  • 进步类的通明和复用

    • Target 指标角色,他的具体实现都委托给了源角色
  • 现有的类可能复用,并且不须要扭转逻辑
  • 指标类和适配器类解耦,进步程序的扩展性
  • 很多场景下合乎开闭准则

    • 每次只须要新增类就行了

毛病

  • 减少类

    • 进步了零碎的复杂性
  • 减少了代码浏览难度,升高了代码可读性,过多应用适配器会让零碎代码变得凌乱

留神

  • 适配器模式在具体设计阶段不要思考,他不是为了解决开发中的问题
  • 我的项目肯定要恪守依赖导致准则和里氏替换准则

    • 否则即应用适配器也是 巧妇难为无米之炊

问题

适配器个别是什么时候用到

​ 适配器模式是一个弥补模式,或者说是一个“补救”模式,通常用来解决接口不相容的问题,在百分之百的完满设计中是不可能应用到的,什么是百分之百的完满设计?

“千虑”而没有“一失”的设计,然而,再完满的设计也会遇到“需要”变更这个无奈回避的问题,,不论零碎设计得如许完满,都无奈回避新业 务的产生,技术只是一个工具而已,是因为它推动了其余行业的提高和倒退而具备了价值,艰深地说,技术是为业务服务的,因而业务在突飞猛进变动的同时,也对技术提出了同样的要求,在这种要求下,就须要 咱们有一种或一些这样的补救模式诞生,应用这些补救模式能够保障我 们的零碎在生命周期内可能稳固、牢靠、强壮的运行,而适配器模式就 是这样的一个“救世主”,它在需要巨变、业务飞速而导致你极度郁闷、焦躁、解体的时候横空出世,它通过把非本零碎接口的对象包装老本系 统能够承受的对象,从而简化了零碎大规模变更危险的存在。

我的笔记仓库地址 gitee 快来给我点个 Star 吧

退出移动版