【设计模式】适配器模式以及源码应用

1  前言

最近看源码的时候,经常看到适配器模式的出现,所以本文来记录一下什么是适配器模式,它的结构特点是什么呢?以及它在源码中的一些应用。

2  适配器模式

2.1  基本概念

适配器模式,适配两个字最能体现其思想,也可以理解为协调、转换,有点类似我们平时见到的各种转换头的作用,它就是一种东西转变为另一种东西的转换器。

适配器模式(Adapter Pattern)是一种常用的设计模式,用于使一个类的接口与另一个类兼容。

适配器模式允许你将已有的类“包装”起来,使其看起来像是另一个接口的一部分,从而让现有类能够与不兼容的接口一起工作。

适配器器模式要解决的主要问题就是多种差异化类型的接⼝做统⼀输出,解决类接口不兼容的问题。

适配器模式提高了系统的灵活性和可扩展性,尤其是在需要复用现有类或实现不同系统间交互时非常有用。

2.2  结构特点

适配器模式涉及以下几个角色:

目标:这是期望的接口,适配器将被设计成实现这个接口。

被适配者:这是已有类的接口,它提供了一些有用的功能,但接口与目标不兼容。

适配器:这是一个新的类,它持有被适配者的实例,并且实现目标接口。适配器将被适配者的接口转换成目标接口所期望的形式。

2.3  适用场景

类接口不兼容:当你有一个类,它的接口不符合现有的系统要求时,可以使用适配器模式来调整这个类的接口,使其适应现有系统的需要。

复用现有类:当你想复用一些有用的类,但这些类的接口不符合你的需求时,可以通过适配器模式来改变接口,使其符合你的需求。

系统间的交互:当两个系统之间需要交互,但它们的接口不同时,可以使用适配器模式来实现中间层,使得这两个系统可以相互通信。

3  源码应用

3.1  JDK 中的 RunnableFuture、RunnableAdapter

我们平时用到的线程池 ThreadPoolExecutor,我们提交异步任务的时候,可以提交 Runnable 或者 Callable 的任务。

public Future<?> submit(Runnable task) {...}
public <T> Future<T> submit(Callable<T> task) {...}

但我们都知道线程池最后的落点就是线程,所以异步任务最后都是要给到 Thread 去执行的,而 Thread 我们会发现不管是它的构造器还是属性都只接纳 Runnable 类型的。

private Runnable target;
public Thread(Runnable target) {...}
public Thread(Runnable target, String name) {...}
Thread(Runnable target, AccessControlContext acc) {...}

所以这里就需要用到适配器来去兼容,那就需要一个适配器类能实现 Runnable 接口并能接收 Callable 参数,统一 Runnable、Callable 两者的行为,它就是 RunnableFuture(这是一个接口,它的具体实现类是 FutureTask)。

我们看线程池提交任务:

// 提交 Runnable 的
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
// 提交 Callable 的
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

可以看到提交后,都会用 newTaskFor 把 Runnable 和 Callable 都包装成了 RunnableFuture 的实现类 FutureTask。

public interface RunnableFuture<V> extends Runnable, Future<V> {...}
public class FutureTask<V> implements RunnableFuture<V> {...}

可以看到 RunnableFuture 继承了 Runnable 所以它的实现类可以作为参数放到 Thread 中去执行。

RunnableFuture 继承了 Future,所以可以通过 get 方法获取到异步任务执行的结果。

它的实现类 FutureTask 在接收到 Callable 或者 Runnable 内部都转换为了 Callable,所以内部还用 RunnableAdapter 对 Runnable 又做了一层适配。

// FutureTask 接收 Runnable
private Callable<V> callable;
public FutureTask(Runnable runnable, V result) {
    // 将 Runnable 转换为 Callable
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}
// Executors.callable 方法转换
public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    // 适配
    return new RunnableAdapter<T>(task, result);
}
// RunnableAdapter 实现 Callable,并能接收 Runnable 的参数,实际执行的时候,调用内部的 Runnable
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;
    }
}
// FutureTask 接收 Callable
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}

可以看到线程池这里涉及到两层适配

RunnableFuture 本身实现了 Runnable ,将 Callable 类型的任务可以交给线程去执行,完成 Callable -> Runnable 的适配。

RunnableAdapter 由于 FutureTask 内部统一逻辑为处理 Callable 类型的,所以当接收到 Runnable 类型的,通过 RunnableAdapter 完成 Runnable -> Callable 的适配,统一内部逻辑。

3.2  SpringValidatorAdapter

前面 我们看的【SpringBoot】@Validated @Valid 参数校验概述以及使用方式【SpringBoot】@Validated @Valid 注解校验时机实现原理,关于参数校验的两类体系,一个是 javax 包里的 @Valid 一个是 Spring 本身的 @Validated。

@Valid 本身的体系已经能完成很多约束的校验,而在 Spring 中想把它融合进来,所以 SpringValidatorAdapter 就出现了。

可以看到 SpringValidatorAdapter 实现了 Validator(Spring自己的) 和 Validator(javax包里的),并且持有 javax.validation.Validator 的属性。

public interface SmartValidator extends Validator {...}
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {

    private javax.validation.Validator targetValidator;
    public SpringValidatorAdapter(javax.validation.Validator targetValidator) {
        Assert.notNull(targetValidator, "Target Validator must not be null");
        this.targetValidator = targetValidator;
    }
    void setTargetValidator(javax.validation.Validator targetValidator) {
        this.targetValidator = targetValidator;
    }
    ...
}

SpringValidatorAdapter 的子类也是 Spring 默认的校验器 LocalValidatorFactoryBean,在 InitializingBean 执行 afterPropertiesSet 的时候,通过构造 Configuration 继而得到 ValidatorFactory 继而通过工厂得到 Validator(javax包里的),然后通过 setTargetValidator 方法给属性赋值。

public class LocalValidatorFactoryBean extends SpringValidatorAdapter
        implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
    public void afterPropertiesSet() {
        Configuration<?> configuration;
        ...
        // Allow for custom post-processing before we actually build the ValidatorFactory.
        postProcessConfiguration(configuration);

        this.validatorFactory = configuration.buildValidatorFactory();
        setTargetValidator(this.validatorFactory.getValidator());
    }
}

继而在参数校验的时候,可以使用到 javax.validation.Validator 的功能。

// private javax.validation.Validator targetValidator;
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
    if (this.targetValidator != null) {
        processConstraintViolations(
                this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
    }
}

所以 SpringValidatorAdapter 完成了 javax 包里的 Validator 到 Spring 中的适配融合。

4  小结

好啦,关于适配器模式的就介绍到这里,其实方向有点反了,应该先准备好设计模式的,然后边看源码的过程中发现有用到,再贴里边,所以之前看过的涉及到设计模式有点忘了都= =,后续回忆起来别的地方我再补进来,有理解不对的地方还请指正哈。

posted @ 2024-09-27 07:10  酷酷-  阅读(23)  评论(0编辑  收藏  举报