栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

适配器模式(SpringMVC、线程源码分析)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

适配器模式(SpringMVC、线程源码分析)

目录
  • 适配器模式
    • 类适配器模式
    • 对象适配器模式
    • 缺省适配器模式(接口适配器模式)
  • 适配器模式的源码级应用
    • 在SpringMVC中的应用
    • 在线程中的应用

适配器模式

适配器模式定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。——《设计模式:可复用面向对象软件的基础》

下图中是一个生活中的例子,标准的AC插头没办法直接使用欧式电源插座,通过交流电适配器,插头和插座之间就可以正常工作了。
再比如,生活中给手机充电,中国家用电的电压为220V,如果给手机充电直接使用220V的电压,手机就该爆炸了,所以充电时使用的充电器就承担了适配器的角色,将220V的电压转换成5V电压,使手机能正常充电。

适配器模式中主要有三种角色,适配器将被适配者中的方法适配为目标接口中的方法:

  • 目标:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
  • 适配者:已有接口,但是和客户器期待的接口不兼容
  • 适配器:将已有接口转换成目标接口
类适配器模式

适配器实现目标接口,继承适配者,适配器重写目标接口中的方法,在该方法内部可以调用所继承的适配者的方法来实现适配

举例:将220V电压适配成5V

适配者:已经存在的类,但能被新的需求直接使用,需要进行一定的转换

public class Voltage220V {

    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "V");
        return src;
    }
}

目标接口:规定需要的接口类型

public interface IVoltage5V {
    int output5V();
}

适配器:实现目标接口,继承适配者

public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
        int srcV = output220V();
        int dstV = srcV/44;
        System.out.println("电压=" + dstV + "V");
        return dstV;
    }
}

测试:给手机充电

public class Phone {

    // 充电
    public void charging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压为5V可以充电");
        } else {
            System.out.println("电压为不是5V,不可以充电");
        }
    }
}

类适配器模式注意事项和细节

  1. Java是单继承机制,所以类适配器需要继承适配者类这一点算是一个缺点,因为这要求目标接口是一个接口,有一定局限性。
  2. 适配者类的方法在适配器中都会暴露出来,也增加了使用成本。
  3. 由于继承了适配者类,所以它可以根据需要重写适配者类的方法,使得适配器的灵活性增强了。
对象适配器模式
  1. 基本思路和类适配器模式相同,只是将适配器类做修改,不是继承适配者,而是持有适配者类的实例,以解决兼容性问题。即:聚合适配者,实现目标接口。
  2. 根据 “合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种。

举例:将220V电压适配成5V(只是在适配器类处有所不同)

适配器:实现目标接口,组合/聚合适配者,这里采用聚合和组合的方式都可以,根据自己实际需要选择

public class VoltageAdapter implements IVoltage5V {
	private Voltage220V voltage220V = new Voltage220V();
    @Override
    public int output5V() {
        int srcV = output220V();
        int dstV = srcV/44;
        System.out.println("电压=" + dstV + "V");
        return dstV;
    }
}

对象适配器模式的注意事项和细节

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成服用原则,只用组合代替继承,所以它解决了类适配器必须继承适配者的局限性问题,也不再要求目标一定是接口。
  2. 使用成本低,更灵活。
缺省适配器模式(接口适配器模式)

当一个接口中有多个方法,但调用者并不想实现该接口的所以方法时,可以使用缺省适配器模式。

  1. 原理是,定义一个抽象类来默认实现接口中的所有方法,即:实现接口,重写接口中的所有方法,但不做具体实现
  2. 调用者通过继承抽象类来重写需要的方法

举例:缺省适配器

目标接口:目标接口中有多个方法,但我不想实现接口中的所有方法,只想实现其中一个或两个

public interface IVoltage5V {
    void m1();
    void m2();
    void m3();
    void m4();
}

适配器:是一个抽象类,不能被实例化,重写了接口的所有方法,但不做具体实现

public abstract class AbstractVoltage5V implements IVoltage5V {
    @Override
    public void m1() {

    }

    @Override
    public void m2() {

    }

    @Override
    public void m3() {

    }

    @Override
    public void m4() {

    }
}

测试:匿名内部类方式

public class Main {
    public static void main(String[] args) {
        IVoltage5V m1 = new AbstractVoltage5V() {
            @Override
            public void m1() {
                System.out.println("m1");
            }
        };
        m1.m1();
    }
}

hint:这里不能使用Lambda表达式,Lambda转换的目标类型必须是接口

适配器模式的源码级应用 在SpringMVC中的应用

DispatcherServlet的doDispatch方法是SpringMVC工作的主要方法,下面仅保留了MVC流程中关键的代码,并对此进行了注释说明

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        
        mappedHandler = getHandler(processedRequest);

        
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());


        
        applyDefaultViewName(processedRequest, mv);

        
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

确定当前请求的处理程序

@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

确定当前请求的处理程序适配器

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");
	}

查看HttpRequestHandlerAdapter适配器源代码

public class HttpRequestHandlerAdapter implements HandlerAdapter {

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

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

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

}

可以看出,适配器和适配者间是一一对应的,HttpRequestHandlerAdapter 中已经默认指定了它的适配者是HttpRequestHandler。

总结

这里使用到的适配器模式中,HandlerAdapter接口担任目标,HadnlerAdapter的实现类担任适配器,各种处理器担任适配者。

这里适配器模式的使用不是很明显,先看一下适配器模式的定义:适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。

分析:

  1. 原本的处理器对象是Object类型,可以通过if-else来转换类型,但违背开闭原则,不符合客户预期的接口。
  2. 为满足客户需求,定义了一个HandlerAdapter接口,里面的方法是客户期待的方法。
  3. 创建具体处理器的适配器,实现HandlerAdapter接口,在方法参数中传入适配者(处理器对象)
在线程中的应用

创建线程的方式有三种:

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法
  3. 实现Callable接口,重写call方法
    java中是单继承,所以继承Thread会有一定的局限性,一般使用后两种。

FutureTask类有两中构造器:

public FutureTask(Callable callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
}

public static  Callable callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter(task, result);
}

可以看到,FutureTask(Runnable runnable, V result)构造器内部调用Executors.callable方法,在该方法的内部返回了一个new RunnableAdapter(task, result),RunnableAdapter就是一个适配器,将Runnable适配为Callable

static final class RunnableAdapter implements Callable {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

RunnableAdapter是Executors的一个静态内部类,其实现了Callable接口内部聚合了Runnable,将传入Runnable对象适配为Callable,这是典型的对象适配器模式

暑期编程PK赛 得CSDN机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1016343.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号