本文通过老王应用纸质书籍浏览小王应用电子书籍的故事,具体阐明设计模式中的结构型设计模式之适配器模式,别离对对象适配器和类适配器代码实现,最初为了加深了解,会列举适配器设计模式在JDK和Spring源码中的利用。
读者能够拉取残缺代码到本地进行学习,实现代码均测试通过后上传到码云。
一、引出问题
自从小王被老王赶出家门当前,老王过了几天舒心的日子,在家里的书架上买了许许多多的纸质书。
有一天,小王过够了野人生存回来了,小王也是一个喜爱读书的人,然而小王不喜爱纸质书,就要求老王将这些书换成电子版。
老王立马就不开心了,这是我不晓得破费多少个日夜才设计好的书架,给你换成电子版的不仅要花费我大量的精力扭转原有书架的构造,再想找我想看的书得有多难,而且老李来了想看纸质版怎么办,我还要再换回去吗?
小王随即想到了一种解决思路:这些书当初合乎你的格调,应该设计一种模式,让这些书也能合乎我的需要,让咱们俩能够在一起读书,既不扭转你的书架构造,又能扩大它的性能。
老王称心的点了拍板,你说的不错,这实际上就是结构型设计模式中的适配器模式。
二、概念与应用
援用Gof中对适配器设计模式的概念:将一个类的接口转化成客户心愿的另一个接口,因为接口不兼容而不能一起工作的类能够一起工作。
很显然,在适配器设计模式中应该有三个角色。
指标类:Target,该角色把其余类转换为咱们冀望的接口,能够是一个抽象类或接口,也能够是具体类。
被适配者类(源): Adaptee ,原有的接口,也是心愿被适配的接口。 适配器: Adapter, 将被适配者和指标抽象类组合到一起的类。
在咱们的理论案例中,老王的纸质书很显著应该是属于被适配者,小王的电子版就是指标类,适配器应该是能调用老王的纸质书,并应用一些相干的业务办法转化成电子版,比方调用老王书之前买一个扫描仪,在老王书调进去当前扫描书籍。
既然适配器中要调用老王的纸质书,调用它的办法应该是有两种实现形式。
一是间接继承老王,那样就能够间接调用老王的办法了。
二是在适配器中创立老王的对象,而后再调用老王的办法。
这其实对应了适配器的两种形式,依据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
咱们先看类适配器实现形式:
被适配者类:
/** * 源对象 * @author tcy * @Date 04-08-2022 */public class AdapteePaperReading { public void readPaper(){ System.out.println("这是老王读的纸质书...(被适配者办法)"); }}
指标对象:
/** * 指标对象 */public interface TargetOnlineReading { public void ReadOnline();}
适配器:
/** * @author tcy * @Date 04-08-2022 */public class Adapter extends AdapteePaperReading implements TargetOnlineReading{ @Override public void ReadOnline() { System.out.println("买一个扫描仪..."); readPaper(); System.out.println("拿到纸质书扫描为电子书..."); }}
客户端:
/** * @author tcy * @Date 04-08-2022 */public class Client { public static void main(String[] args) { Adapter adapter=new Adapter(); adapter.ReadOnline(); }}
以上就实现类适配器,如果咱们要实现对象适配器也很简略,指标对象和被适配者都不变,须要扭转的是适配器代码
/** * @author tcy * @Date 04-08-2022 */public class Adapter implements TargetOnlineReading { // 适配者是对象适配器的一个属性 private AdapteePaperReading adaptee = new AdapteePaperReading(); @Override public void ReadOnline() { System.out.println("买一个扫描仪..."); adaptee.readPaper(); System.out.println("拿到纸质书扫描为电子书..."); }}
这样老王和小王就能在一起读书了。但这种形式只能作为零碎的一种补救措施,而不是在零碎设计之初就思考这种形式,如果老王有十个八个儿子都要求依照他们的习惯来,那零碎就会相当的简单,无异于一场劫难。而是应该思考重做书架,将各种状况都思考进去。
须要阐明的是,类适配器之间的耦合度比后者高,且要求程序员理解现有组件库中的相干组件的内部结构,所以利用绝对较少些。
三、利用
案例有一些僵硬,为了加深对适配器设计模式的把握,咱们介绍该模式在Jdk源码和Spring中的利用。
1、JDK利用
JDK应用适配器的典型例子是Java线程池FutureTask类。咱们晓得通过实现接口实现多线程一共有两种形式,Runnable接口和Callable接口。
FutrueTask类中有两个构造方法:
构造方法一:传入参数为Callable接口
// 这是FutureTask的构造方法一public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; }
构造方法二:传入的参数为Runnable接口
// 这是FutureTask的构造方法二public FutureTask(Runnable runnable, V result) { // 调用Executors类中的callable办法进行转化 this.callable = Executors.callable(runnable, result); this.state = NEW; }
在构造方法中实际上加传入的Runnable工作在外部对立被转换为Callable工作。
能够看到这里采纳的是适配器模式,调用RunnableAdapter<T>(task, result)
办法来适配,实现如下:
static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; }}
这样无论是传入Runnalbe还是Callable都能适配工作,这个适配器很简略,就是简略的实现了Callable接口,在call()实现中调用Runnable.run()办法,而后把传入的result作为工作的后果返回。
通过这么一个简略案例能够加深对适配器模式的了解。
2、SpringAOP利用
咱们晓得在Spring的Aop中,应用的 Advice(告诉) 来加强被代理类的性能。
其中Advice的类型有:BeforeAdvice(在执行切点前的告诉)、AfterReturningAdvice(在运行完切点完未返回之前)、ThrowsAdvice(在运行完切点时抛出异样进行的告诉),AfterAdvice(执行完该切点后,进行的告诉)、Around advice(包裹一个办法的执行)
在每个类型 Advice 都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、 ThrowsAdviceInterceptor
Spring须要将每个 Advice 都封装成对应的拦截器类型,返回给容器,这时候采纳的就是适配器类型。
Advice 就相当于适配者,对应的拦截器类型就是指标类。
限于篇幅,有趣味的读者能够到Spring源码中理解具体过程。
3、SpringMVC利用
Spring MVC中的适配器模式次要用于执行指标 Controller 中的申请解决办法。
在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为冀望接口,具体的适配器实现类用于对指标类进行适配,Controller 作为须要适配的类。
当Spring容器启动后,会将所有定义好的适配器对象寄存在一个List汇合中,当一个申请来长期,DispatcherServlet 会通过 handler 的类型找到对应适配器,并将该适配器对象返回给用户,而后就能够对立通过适配器的 hanle() 办法来调用 Controller 中的用于解决申请的办法。
通过适配器模式咱们将所有的 controller 对立交给 HandlerAdapter 解决,免去了写大量的 if-else 语句对 Controller 进行判断,也更利于扩大新的 Controller 类型。
单纯的说苍白无力,咱们手写实现SpringMVC的外围流程,残缺代码曾经上传到码云。
四、总结
既然适配器模式能够扩大原有类的性能,那它和代理模式在肯定水平上不是重合了吗?貌似扩大老王的书架应用代理模式同样是能够实现。
其实咱们看结构型设计模式的定义:结构型模式波及到如何组合类和类以取得更大的构造,结构型类模式采纳继承机制来组合接口或实现。
代理模式与适配器模式都别离有继承、接口方式实现的子分类模式。基于接口实现的代理模式称为动态代理模式、JDK(动静)代理模式,基于继承实现的代理模式称为Cglib(动静)代理模式。
基于接口(同时含类继承)实现的适配器模式称为类适配器模式,(只)基于继承(应用委托)实现的适配器模式称为类适配器模式。
代理模式是为其余类提供一种代理以管制对这个类的拜访。咱们不间接去接触指标类,而是间接操作代理类,代理类再去操作指标类。因为不间接接触指标类,因而咱们能够在代理类的同名办法中增加或删除功能模块,而不必去批改指标类的原办法。
而适配器模式则次要是协调事实与需要的差别,缩小对已有代码的改变,适配不同的接口、类类型。
我的项目施行中可能会呈现这样的状况:以后已实现的我的项目的某一个包内的各个类实现了一些特定的接口,而客户提出了新的需要,要求实现他所指定的那些接口(摈弃原有的办法或接口),但其业务细节却是雷同、齐全一样的。此时,咱们可能并不想复制粘贴原代码到新的办法中去,这就须要将一个类的接口转换成新需要的另一个接口。
实现形式有很多,没有必要咬文嚼字纠结应用哪种设计模式,设计模式自身就是很类似,只有能简洁开发流程,让咱们的代码更好的工作就是完满的。具体应用哪一种就须要读者熟练掌握各种设计模式了,并认真领会他们各自的劣势。
举荐读者,参考软件设计七大准则 认真浏览往期的文章,认真领会。
创立型设计模式:
一、设计模式之工厂办法和形象工厂
二、设计模式之单例和原型
三、设计模式之建造者模式
结构型设计模式:
四、设计模式之代理模式