【設計模式】介面卡模式以及原始碼應用

酷酷-發表於2024-09-27

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 小結

好啦,關於介面卡模式的就介紹到這裡,其實方向有點反了,應該先準備好設計模式的,然後邊看原始碼的過程中發現有用到,再貼裡邊,所以之前看過的涉及到設計模式有點忘了都= =,後續回憶起來別的地方我再補進來,有理解不對的地方還請指正哈。

相關文章