本文通过老王使用纸质书籍阅读小王使用电子书籍的故事,详细说明设计模式中的结构型设计模式之适配器模式,分别对对象适配器和类适配器代码实现,最后为了加深理解,会列举适配器设计模式在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 就相当于适配者,对应的拦截器类型就是目标类。

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(动态)代理模式。

基于接口(同时含类继承)实现的适配器模式称为类适配器模式,(只)基于继承(使用委托)实现的适配器模式称为类适配器模式。

代理模式是为其他类提供一种代理以控制对这个类的访问。我们不直接去接触目标类,而是直接操作代理类,代理类再去操作目标类。因为不直接接触目标类,因此我们可以在代理类的同名方法中添加或删除功能模块,而不用去修改目标类的原方法。

而适配器模式则主要是协调现实与需求的差异,减少对已有代码的改动,适配不同的接口、类类型。

项目实施中可能会出现这样的情况:当前已完成的项目的某一个包内的各个类实现了一些特定的接口,而客户提出了新的需求,要求实现他所指定的那些接口(抛弃原有的方法或接口),但其业务细节却是相同、完全一样的。此时,我们可能并不想复制粘贴原代码到新的方法中去,这就需要将一个类的接口转换成新需求的另一个接口。

实现方式有很多,没有必要咬文嚼字纠结使用哪种设计模式,设计模式本身就是很相似,只要能简洁开发流程,让我们的代码更好的工作就是完美的。具体使用哪一种就需要读者熟练掌握各种设计模式了,并认真体会他们各自的优势。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对Devmax的支持。如果你想了解更多相关内容请查看下面相关链接

Java设计模式之适配器模式的更多相关文章

  1. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  3. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  4. PHP对象、模式与实践之高级特性分析

    这篇文章主要介绍了PHP对象、模式与实践之高级特性,结合实例形式分析了php面向对象程序设计中的静态属性和方法、抽象类、接口、拦截器、克隆对象等概念与简单实现方法,需要的朋友可以参考下

  5. Java实现世界上最快的排序算法Timsort的示例代码

    Timsort 是一个混合、稳定的排序算法,简单来说就是归并排序和二分插入排序算法的混合体,号称世界上最好的排序算法。本文将详解Timsort算法是定义与实现,需要的可以参考一下

  6. Java日期工具类的封装详解

    在日常的开发中,我们难免会对日期格式化,对日期进行计算,对日期进行校验,为了避免重复写这些琐碎的逻辑,我这里封装了一个日期工具类,方便以后使用,直接复制代码到项目中即可使用,需要的可以参考一下

  7. Java设计模式之模板方法模式Template Method Pattern详解

    在我们实际开发中,如果一个方法极其复杂时,如果我们将所有的逻辑写在一个方法中,那维护起来就很困难,要替换某些步骤时都要重新写,这样代码的扩展性就很差,当遇到这种情况就要考虑今天的主角——模板方法模式

  8. Java 中 Class Path 和 Package的使用详解

    这篇文章主要介绍了Java 中 Class Path和Package的使用详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

  9. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  10. Java一维数组和二维数组元素默认初始化值的判断方式

    这篇文章主要介绍了Java一维数组和二维数组元素默认初始化值的判断方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部