适配器模式
- 适配器模式(Adapter Pattern,其别名为包装器(Wrapper))将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作;
- 适配器模式属于结构型模式;
- 主要分为三类:类适配器模式,对象适配器模式,接口适配器模式;
工作原理:
- 将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;
- 从用户的角度看不到被适配者,是解耦的 ;
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;
- 用户收到反馈结果,感觉只是和目标接口交互;
示例:手机充电问题,将 220V 电压通过充电器转换为 5V 电压;
类适配器模式
适配器接口
手机需要使用 5V 的电压,因此需要将 220V 电压转换为 5V;
public interface IVoltage5V {
int output5V();
}
220V 的电压类
public class IVoltage220V {
public int output220V() {
int src = 220;
System.out.println("电压 = " + src + "V");
return src;
}
}
适配器类
调用 IVoltage220V 的 220V 方法,将其转换为 5V;
public class VlotageAdapter extends IVoltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = super.output220V();
int dstV = srcV / 44;
System.out.println("进行转换电压");
return dstV;
}
}
需要适配的类
Phone
public class Phone {
//只需要5V电压
public void charging(IVoltage5V iVoltage5V) {
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5V,可以充电了");
} else {
System.out.println("电压不等于5V,不能充电");
}
}
}
客户端调用
这里可以看出,直接让需要适配的类调用它本身的方法即可;方便扩展,如果加入其它的电器,也需要 5V 的电压,那么直接注入适配器类即可;
public class TestClassAdapter {
public static void main(String[] args) {
Phone phone = new Phone();
VlotageAdapter adapter = new VlotageAdapter();
phone.charging(adapter);
}
}
结果如下
电压 = 220V
进行转换电压
电压为5V,可以充电了
对象适配器模式
- 基本思路和类的适配器模式相同,只是将适配器类(Adapter)作修改,不是继承被适配的类(src 类),而是持有 src 类的实例,以解决兼容性的问题;即,持有 src 类,实现 dst 类接口(适配器接口), 完成 src --> dst 的适配;
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系;
- 对象适配器模式是适配器模式常用的一种;
Client 只是一个测试类;
适配器接口
public interface IVoltage5V {
int output();
}
被适配的类 src
public class IVoltage220V {
public int output220V() {
int src = 220;
System.out.println("电压 = " + src + "V");
return src;
}
}
适配器类
直接注入被适配的 VlotageAdapter 类的实例即可,而不是再去继承;
public class VlotageAdapter implements IVoltage5V {
private IVoltage220V iVoltage220V;
public VlotageAdapter(IVoltage220V iVoltage220V) {
this.iVoltage220V = iVoltage220V;
}
@Override
public int output() {
int dstV = 0;
if(iVoltage220V != null){
int srcV = iVoltage220V.output220V();
System.out.println("转换前 srcV = " + srcV);
dstV = srcV / 44;
System.out.println("适配完成 dstV = " + dstV);
}
return dstV;
}
}
要适配的类 dst
该类和之前一样,不发生改变;
客户端测试使用
public class Clinet {
public static void main(String[] args) {
Phone phone = new Phone();
VlotageAdapter adapter = new VlotageAdapter(new IVoltage220V());
phone.charging(adapter);
}
}
接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;
适用于一个接口不想使用其所有的方法的情况。
适配接口
public interface Interface3 {
void m1();
void m2();
void m3();
}
抽象适配器类
public class AbsAdapter implements Interface3{
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
}
客户端测试类
当然,也可以编写要适配的类来继承抽象适配器类 AbsAdapter,并且只重写需要的方法即可;
public class Client {
public static void main(String[] args) {
AbsAdapter adapter = new AbsAdapter() {
@Override
public void m1() {
System.out.println("只重写该方法");
}
};
adapter.m1();
}
}
SpringMVC 的适配器模式
首先,回顾 SpringMVC 中执行请求的流程:
-
用户发送请求至前端控制器 DispatcherServlet 类中的 doDisptch 方法;
-
根据 HandlerMapping 中保存的请求映射信息,调用方法 getHandler 获取出能够处理当前请求的处理器执行链(HandlerExecutionChain)(包含拦截器);
-
根据当前处理器(即,xxxController 对象)找到它对应的 HandlerAdapter(适配器设计模式);
-
如果存在拦截器,那么拦截器的 preHandle 方法先执行;
-
接着 xxxController 对象对应的适配器对象就会执行目标方法,如下,并且会返回一个 ModelAndView 对象 mv;
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
执行完目标方法后,继续执行拦截器的 postHandle 方法;
-
接着就会将之前目标方法返回的 ModelAndView 对象传入方法 processDispatchResult 来进行结果集的处理;
-
然后会调用 ViewResolver 视图解析器进行视图解析;
-
ViewResolver 解析后返回具体的 View 对象并且对 View 对象进行视图渲染(即将模型数据填充至视图中);
-
最终 DispatcherServlet 响应用户。
主要就是以上的步骤;
在 dispatch 方法中,就会使用到适配器模式;
主要就是当请求发送过来后,会使用 HttpServletRequest 对象获取到对应的 HandlerExecutionChain 的 handle 的对象:
mappedHandler = getHandler(processedRequest);
然后通过该 handle 对象获取对应适配器对象 ha :
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
在方法 getHandlerAdapter 方法中,通过遍历来寻找 handler 对象对应的 适配器 对象 ha :
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//通过for循环寻找到对应的适配器
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
//supports方法是适配器接口中的一个方法
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
只有找到对应的适配器对象,才能够调用对应的方法 handle(processedRequest, response, mappedHandler.getHandler()) 执行Controller 的目标方法:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
上述的过程就是适配器模式,下图为大致的类图分析:
总结:
- Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类 ;
- 适配器代替 Controller 执行相应的方法;
- 因此,扩展 Controller 时,只需要增加一个适配器类就完成了 SpringMVC 的扩展;
模拟DispatchServlet核心流程
多种Controller实现
public interface Controller {
}
class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}
class SimpleController implements Controller {
public void doSimpleHandler() {
System.out.println("simple...");
}
}
class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}
适配器接口及对应的适配器类
public interface HandlerAdapter {
//模拟SpringMVC中的适配器接口中的方法supports和handler
boolean supports(Object handler);
void handler(Object handler);
}
class HttpHandlerAdapter implements HandlerAdapter{
@Override
public boolean supports(Object handler) {
//判断类型是否正确,如果正确就返回true,那么就会返回一个适配器实例
return (handler instanceof HttpController);
}
@Override
public void handler(Object handler) {
((HttpController)handler).doHttpHandler();
}
}
class SimpleHandlerAdapter implements HandlerAdapter{
@Override
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
@Override
public void handler(Object handler) {
((SimpleController)handler).doSimpleHandler();
}
}
class AnnotationHandlerAdapter implements HandlerAdapter{
@Override
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
@Override
public void handler(Object handler) {
((AnnotationController)handler).doAnnotationHandler();
}
}
DispatchServlet 类的模拟
public class DispatchServlet {
private static LinkedList<HandlerAdapter> handlerAdapters = new LinkedList<>();
public DispatchServlet() {
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
handlerAdapters.add(new AnnotationHandlerAdapter());
}
public void doDispatch() {
HttpController httpController = new HttpController();
HandlerAdapter adapter = getHandlerAdapter(httpController);
adapter.handler(httpController);
}
/**
* 寻找适配器实例
* @param controller 需要判断的controller类型
* @return 返回对应的适配器对象,如果没有返回null
*/
private HandlerAdapter getHandlerAdapter(Controller controller) {
for (HandlerAdapter handlerAdapter : handlerAdapters) {
if(handlerAdapter.supports(controller)){
return handlerAdapter;
}
}
return null;
}
public static void main(String[] args) {
//开始模拟
new DispatchServlet().doDispatch();
}
}
结果
http...
场景:
-
类适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可;
-
对象适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个适配器类,持有原类的一个实例,在适配器类的方法中,调用实例的方法就行;
-
接口适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类,实现所有方法,我们写别的类的时候,继承抽象类并且实现需要的方法即可。
优点: -
客户端通过适配器可以透明地调用目标接口;
-
复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类;
-
将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。
装饰者模式
装饰者模式:动态的将新功能附加到对象上;在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP);
参考传送门
示例:
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡),ShortBlack,LongBlack(美式 咖啡),Decaf(无因咖啡)
- 调料:Milk,Soy(豆浆),Chocolate;
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便 ;
Milk 中使用到了 AbsBeverage 的子类也就是 LongBlack 的实例,并且 Milk 类不是不能离开 LongBlack 实例,因此它们的关系就是一个聚合的关系,而不是组合;
抽象饮品类
public abstract class AbsBeverage {
public abstract String getDescription();
public abstract BigDecimal cost();
}
美式咖啡(继承饮品抽象类)
public class LongBlack extends AbsBeverage{
@Override
public String getDescription() {
return "美式咖啡";
}
@Override
public BigDecimal cost() {
return new BigDecimal("3.0");
}
}
所有佐料类的抽象类
public abstract class CondimentDecorator extends AbsBeverage{
public abstract String getDescription();
}
佐料类(需要给美式咖啡对象进行包装)
public class Milk extends CondimentDecorator{
//注入需要包装的类的实例
private AbsBeverage beverage;
public Milk(AbsBeverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + "加入Milk,";
}
@Override
public BigDecimal cost() {
return beverage.cost().add(new BigDecimal("1.5"));
}
}
客户端测试
public class Client {
public static void main(String[] args) {
AbsBeverage beverage = new LongBlack();
//将美式咖啡加入Milk类中进行一次包装
Milk longBlackAndMilk = new Milk(beverage);
System.out.println(longBlackAndMilk.getDescription() + " Cost = " + longBlackAndMilk.cost());
}
}
结果
美式咖啡加入Milk, Cost = 4.5
优点
- 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能;(继承在扩展功能是静态的,必须在编译时就确定好,而使用装饰者可以在运行时决定,装饰者也建立在继承的基础之上的)
- 通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果;
- 符合开闭原则;
缺点 - 会出现更多的代码,更多的类,增加程序的复杂性;
- 动态装饰和多层装饰时会更复杂。(使用继承来拓展功能会增加类的数量,使用装饰者模式不会像继承那样增加那么多类的数量但是会增加对象的数量,当对象的数量增加到一定的级别时,无疑会大大增加我们代码调试的难度)
代理模式
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象;
- 这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;
- 被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象 ;
- 代理模式有不同的形式,主要有三种:静态代理,动态代理 (JDK代理或接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 它是属于动态代理的范畴);
示例:体育老师零时有事,需要数学老师张东升进行体育课的代理;
静态代理
Teacher接口
public interface Teacher {
void teach();
}
GymTeacher被代理类
public class GymTeacher implements Teacher{
@Override
public void teach() {
System.out.println("体育老师正在授课中...");
}
}
MathTeacherProxy代理类
public class MathTeacherProxy implements Teacher{
private GymTeacher teacher;
public MathTeacherProxy(GymTeacher teacher){
this.teacher = teacher;
}
@Override
public void teach() {
//进行代理,可以前后等位置做一些自己的工作
System.out.println("张东升老师代理开始,可以做一些其它工作");
teacher.teach();
System.out.println("张东升老师代理结束,可以做一些其它工作");
}
}
客户端测试
public class Client {
public static void main(String[] args) {
MathTeacherProxy proxy = new MathTeacherProxy(new GymTeacher());
proxy.teach();
}
}
动态代理
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理;
- 代理对象的生成,是利用 JDK 的 API ,动态的在内存中构建代理对象;
- 动态代理也叫做:JDK代理或者接口代理;
Teacher 接口和 GymTeacher 和之前一样;
MathTeacherDynamicProxy JDK 代理类
public class MathTeacherDynamicProxy {
private Object obj;
//将需要代理的对象注入
public MathTeacherDynamicProxy(Object obj) {
this.obj = obj;
}
public Object getProxyInstance() {
//调用反射包下的 Proxy 的 newProxyInstance 方法进行 JDK 代理
//传入的参数依次:被代理类的类加载器,被代理类实现的接口类数组,和一个匿名内部类InvocationHandler
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK 张东升开始代理");
//传入被代理对象obj,然后执行方法
Object resultVal = method.invoke(obj, args);
System.out.println("JDK 张东升结束代理");
return resultVal;
}
});
}
}
Cglib 代理
- 静态代理和 JDK 代理模式都要求目标对象去实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理这就是 Cglib 代理;
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理之中;
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 Java 接口;它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截;
- 在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,使用 JDK 代理;
- 目标对象不需要实现接口,使用 Cglib 代理;
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类;
-
引入 Cglib 的 jar 包:
-
在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException ;
-
目标对象的方法如果为 final/static ,那么就不会被拦截,即不会执行目标对象额外的业务方法;
目标对象类
public class TeacherDao {
public void teach(){
System.out.println("老师授课中,目前是 Cglib 代理,不需要实现接口");
}
}
代理类
实现 Cglib 的 MethodInterceptor 接口的 intercept 方法;
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//将被代理对象传入
public ProxyFactory(Object target) {
this.target = target;
}
/**
* @return 返回一个代理对象
*/
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,也就是代理对象
return enhancer.create();
}
//实现 MethodInterceptor 的 intercept 方法,会调用目标对象的方法
//method 目标对象要执行的方法
//args 方法参数
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib 代理模式开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib 代理模式提交");
return returnVal;
}
}
客户端
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao target = new TeacherDao();
//传递目标对象给代理类并获取代理对象
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
//调用方法
proxyInstance.teach();
}
}