1. 概述

本文将介绍一个非经典GoF设计模式但非常实用的模式——管道模式。这个模式能优雅解决复杂问题并优化应用架构,Java本身也提供了内置支持(最后会讨论)。对于有经验的开发者来说,掌握它能显著提升代码的灵活性。

2. 相关模式对比

管道模式常与责任链模式装饰器模式比较,三者有相似处但核心差异明显。下面用列表快速对比关键区别:

模式 核心特点 返回值灵活性 典型应用场景
责任链模式 显式声明步骤链,请求沿链传递 ❌ 通常无返回值 请求处理(如过滤器链)
装饰器模式 递归嵌套对象,动态添加行为 ❌ 无返回值 功能增强(如IO流包装)
管道模式 数据流经处理链,输入输出类型可变 ✅ 完全灵活 数据转换(如ETL处理)

2.1 责任链模式

管道模式常被误认为责任链的变体,但关键区别在于返回值处理

  • 经典责任链的handleRequest()方法通常无返回值(如图示): Concrete Handler
  • 虽可强行添加返回值,但会破坏模式本意,属于"踩坑"行为

2.2 装饰器模式

装饰器通过递归嵌套实现行为叠加,表面上与管道无关,但执行流程高度相似decorator

⚠️ 注意:经典装饰器模式无返回值,主要用于状态修改。若强行用于数据处理,会导致:

  • 代码过度复杂
  • 时序依赖管理困难
  • 不如管道模式直观

3. 管道模式详解

管道模式的核心是创建处理链并让数据流经其中。相比责任链和装饰器,它的杀手锏是输入输出类型完全灵活

3.1 不可变管道实现

先定义基础接口(注意类型参数命名区别于Java规范):

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; // 类型安全由构造保证
    }
}

关键设计点:

  • withNextPipe()返回新实例,避免并发问题
  • 类型参数链式传递(IN→OUT→NEW_OUT)
  • 处理时使用Object中转,但类型安全由构造保证

3.2 简化管道实现

可完全去掉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)); // 递归调用
    }
}

✅ 优点:

  • 代码极简
  • 递归结构清晰

❌ 缺点:

  • 整个管道被隐藏在方法调用中
  • 调试困难(无法直接获取管道结构)

3.3 函数式方案(推荐)

Java内置的Function接口天然支持管道模式:

// 使用Function替代自定义Pipe
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); // 返回"12"

进阶技巧:结合BiFunction处理多参数

BiFunction<Integer, Integer, Integer> add = Integer::sum;
BiFunction<Integer, Integer, Integer> mul = (a, b) -> a * b;
Function<Integer, String> toString = Object::toString;

// 使用柯里化转换BiFunction
BiFunction<Integer, Integer, String> pipeline = add
    .andThen(a -> mul.apply(a, 2)) // 固定第二个参数
    .andThen(toString);

String result = pipeline.apply(1, 2); // 返回"6"

⚠️ 注意:andThen()要求Function,需用柯里化转换BiFunction(如示例中固定参数)。

4. 总结

管道模式虽未入选GoF经典23式,但绝对是数据流处理的利器。实现方式多样:

  • 自定义不可变管道(强类型安全)
  • 简化接口实现(代码简洁)
  • Java函数式方案(推荐,与Stream API同源)

✅ 最佳实践:

  • 优先使用Function.andThen()
  • 复杂场景才考虑自定义实现
  • 善用Stream API(其操作链本质就是管道)

实际开发中,90%的场景用Stream API足够。只有需要特殊控制流或类型转换时,才需手写管道。


原始标题:Pipeline Design Pattern in Java | Baeldung