天天看點

Java流水線Pipeline設計模式

作者:JAVA微學堂
Java流水線Pipeline設計模式

概述

管道模式背後的主要思想是建立一組操作(管道)并通過它傳遞資料。跟責任鍊和裝飾器模式相比,Pipeline的主要優勢在于它對結果的類型具有靈活性。

管道可以處理任何類型的輸入和輸出。

不可變管道

讓我們建立一個不可變的管道的例子。從管道接口開始:

public interface Pipe<IN, OUT> {
    OUT process(IN input);
}           

這是一個非常簡單的接口,隻有一個方法,它接受輸入并産生輸出。接口是參數化的,我們可以在其中提供任何實作。

現在,讓我們建立一個管道類:

public class Pipeline<IN, OUT> {

    private Collection<Pipe<?, ?>> pipes;

    private Pipeline(Pipe<IN, OUT> pipe) {
        pipes = Collections.singletonList(pipe);
    }

    private Pipeline(Collection<Pipe<?, ?>> pipes) {
        this.pipes = new ArrayList<>(pipes);
    }

    public static <IN, OUT> Pipeline<IN, OUT> of(Pipe<IN, OUT> pipe) {
        return new Pipeline<>(pipe);
    }

    public <NEW_OUT> Pipeline<IN, NEW_OUT> withNextPipe(Pipe<OUT, NEW_OUT> pipe) {
        final ArrayList<Pipe<?, ?>> newPipes = new ArrayList<>(pipes);
        newPipes.add(pipe);
        return new Pipeline<>(newPipes);
    }

    public OUT process(IN input) {
        Object output = input;
        for (final Pipe pipe : pipes) {
            output = pipe.process(output);
        }
        return (OUT) output;
    }
}           

因為我們需要一個類型安全級别,并且不允許使管道失效,是以在添加新管道時,将産生一個新的獨立管道。

簡單管道

我們可以稍微簡化上面的例子,并完全去掉Pipeline類:

public interface Pipe<IN, OUT> {
    OUT process(IN input);

    default <NEW_OUT> Pipe<IN, NEW_OUT> add(Pipe <OUT, NEW_OUT> pipe) {
        return input -> pipe.process(process(input));
    }
}           

與以前使用管道的實作相比,此解決方案非常簡單和靈活。

改進

我們可以用現有的Function接口替代它:

public interface Function<T, R> {
    //...
    R apply(T t);
    //...
}           

此外,Function接口包含兩個有用的方法,其中一個是andThen:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}           

我們可以使用它來代替以前的add方法。此外,Function接口提供了一種在管道開始時添加函數的方法:

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}           

通過使用Function,我們可以建立非常靈活和易于使用的管道:

@Test
void whenCombiningThreeFunctions_andInitializingPipeline_thenResultIsCorrect() {
    Function<Integer, Integer> square = s -> s * s;
    Function<Integer, Integer> half = s -> s / 2;
    Function<Integer, String> toString = Object::toString;
    Function<Integer, String> pipeline = square.andThen(half)
        .andThen(toString);
    String result = pipeline.apply(5);
    String expected = "12";
    assertEquals(expected, result);
}           

我們可以使用BiFunctions擴充管道:

@Test
void whenCombiningFunctionAndBiFunctions_andInitializingPipeline_thenResultIsCorrect() {
    BiFunction<Integer, Integer, Integer> add = Integer::sum;
    BiFunction<Integer, Integer, Integer> mul = (a, b) -> a * b;
    Function<Integer, String> toString = Object::toString;
    BiFunction<Integer, Integer, String> pipeline = add.andThen(a -> mul.apply(a, 2))
        .andThen(toString);
    String result = pipeline.apply(1, 2);
    String expected = "6";
    assertEquals(expected, result);
}           

因為andThen方法采用Function,是以我們必須将mul BiFunction轉換為一個Function。

結論

Pipeline模式适用于流式遞歸傳遞輸入和處理後的輸出,對于比較簡單的場景,使用Java函數接口是挺不錯的選項。

繼續閱讀