管道模式:让数据处理像流水线一样优雅

管道模式,说白了就是把数据处理拆成一个个独立的步骤,像工厂流水线一样串起来。每个步骤只管自己的事,做完就传给下一个。我在做中间件框架时,这个模式帮了大忙。

为什么需要管道模式?

你想想看,如果一段数据处理逻辑全写在一个方法里,会是什么样子?

  • 代码又长又臭,根本没法维护
  • 想加个新功能,得改老代码
  • 想复用某个处理步骤?没门
  • 测试起来特别痛苦

管道模式就是来解决这些问题的。它把处理逻辑拆成一个个处理器,每个处理器只做一件事。你可以自由组合、灵活编排。

核心思想:每个处理器只负责一个职责,通过管道串联起来形成处理链。

三个核心角色

管道模式有三个关键角色,我一个个说。

1. 管道接口(Pipeline)

管道接口定义了整个处理流程的骨架。它负责管理处理器链,控制数据流动。

// Java 版本
public interface Pipeline<T> {
    Pipeline<T> addHandler(Handler<T> handler);
    T execute(T input);
}

// C++ 版本
template<typename T>
class Pipeline {
public:
    virtual ~Pipeline() = default;
    virtual Pipeline<T>& addHandler(std::shared_ptr<Handler<T>> handler) = 0;
    virtual T execute(const T& input) = 0;
};

2. 处理器接口(Handler)

处理器接口定义了每个处理步骤的契约。每个处理器实现这个接口,完成自己的那部分工作。

// Java 版本
public interface Handler<T> {
    T process(T input);
}

// C++ 版本
template<typename T>
class Handler {
public:
    virtual ~Handler() = default;
    virtual T process(const T& input) = 0;
};

3. 管道构建器(PipelineBuilder)

构建器负责组装管道。我个人习惯用构建器模式来创建管道,这样代码更清晰。

// Java 版本
public class PipelineBuilder<T> {
    private List<Handler<T>> handlers = new ArrayList<>();
    
    public PipelineBuilder<T> addHandler(Handler<T> handler) {
        handlers.add(handler);
        return this;
    }
    
    public Pipeline<T> build() {
        return new DefaultPipeline<>(handlers);
    }
}

// C++ 版本
template<typename T>
class PipelineBuilder {
private:
    std::vector<std::shared_ptr<Handler<T>>> handlers;
public:
    PipelineBuilder& addHandler(std::shared_ptr<Handler<T>> handler) {
        handlers.push_back(handler);
        return *this;
    }
    
    std::unique_ptr<Pipeline<T>> build() {
        return std::make_unique<DefaultPipeline<T>>(handlers);
    }
};

完整实现示例

来看一个实际的数据处理例子。假设我们要处理用户提交的文本:先去掉空格,再转小写,最后过滤敏感词。

// Java 完整实现
public class DefaultPipeline<T> implements Pipeline<T> {
    private final List<Handler<T>> handlers;
    
    public DefaultPipeline(List<Handler<T>> handlers) {
        this.handlers = new ArrayList<>(handlers);
    }
    
    @Override
    public Pipeline<T> addHandler(Handler<T> handler) {
        handlers.add(handler);
        return this;
    }
    
    @Override
    public T execute(T input) {
        T result = input;
        for (Handler<T> handler : handlers) {
            result = handler.process(result);
        }
        return result;
    }
}

// 具体处理器
public class TrimHandler implements Handler<String> {
    @Override
    public String process(String input) {
        return input.trim();
    }
}

public class LowerCaseHandler implements Handler<String> {
    @Override
    public String process(String input) {
        return input.toLowerCase();
    }
}

public class SensitiveFilterHandler implements Handler<String> {
    private static final Set<String> SENSITIVE_WORDS = Set.of("bad", "ugly");
    
    @Override
    public String process(String input) {
        for (String word : SENSITIVE_WORDS) {
            input = input.replace(word, "***");
        }
        return input;
    }
}

// 使用示例
Pipeline<String> pipeline = new PipelineBuilder<String>()
    .addHandler(new TrimHandler())
    .addHandler(new LowerCaseHandler())
    .addHandler(new SensitiveFilterHandler())
    .build();

String result = pipeline.execute("  Hello Bad World  ");
// 结果: "hello *** world"

管道模式的核心流程图

下面这张图展示了数据在管道中的流动过程:

输入数据 处理器1 去空格 处理器2 转小写 处理器3 过滤敏感词 输出数据 管道(Pipeline) 数据从左到右依次经过每个处理器,最终得到处理结果

中间件链:管道的进阶玩法

管道模式还有一个变体叫中间件链。每个处理器不仅能处理数据,还能决定是否继续传递。这在 Web 框架里特别常见。

// Java 中间件链版本
public interface Middleware<T> {
    void process(T data, Chain<T> chain);
}

public interface Chain<T> {
    void proceed(T data);
}

public class MiddlewarePipeline<T> {
    private final List<Middleware<T>> middlewares;
    private int index = 0;
    
    public MiddlewarePipeline(List<Middleware<T>> middlewares) {
        this.middlewares = middlewares;
    }
    
    public void execute(T data) {
        if (index < middlewares.size()) {
            Middleware<T> middleware = middlewares.get(index++);
            middleware.process(data, this);
        }
    }
}

// 使用示例
public class AuthMiddleware implements Middleware<Request> {
    @Override
    public void process(Request request, Chain<Request> chain) {
        if (request.isAuthenticated()) {
            chain.proceed(request);  // 通过认证,继续
        } else {
            throw new SecurityException("未认证");
        }
    }
}

我的经验:中间件链特别适合做权限校验、日志记录、性能监控这类横切关注点。每个中间件只关心自己的事,互不干扰。

避坑指南

我曾经踩过的坑:

  • 处理器顺序搞错:有一次我把数据校验放在数据清洗前面,结果校验逻辑因为数据格式不对一直报错。后来我养成了先清洗再校验的习惯。
  • 共享状态问题:多个处理器如果共享同一个可变对象,很容易出并发问题。我建议每个处理器只处理自己的输入输出,不要修改共享状态。
  • 异常处理不当:管道中某个处理器抛异常,后面的处理器就收不到数据了。我一般会在管道外层统一做异常处理,或者让每个处理器自己处理异常。

适用场景

场景 说明 推荐度
数据清洗 多个清洗步骤可以拆成独立处理器 ⭐⭐⭐⭐⭐
请求处理 Web框架的过滤器链、中间件链 ⭐⭐⭐⭐⭐
ETL流程 抽取、转换、加载各步骤独立 ⭐⭐⭐⭐
编译管道 词法分析、语法分析、语义分析 ⭐⭐⭐⭐

总结

管道模式的核心价值就四个字:职责分离。每个处理器只做一件事,通过管道串联起来完成复杂任务。我个人觉得,这个模式最大的好处是让代码变得特别好测试——每个处理器都可以单独测,测完再组装。

嗯,如果你正在做一个需要多步骤处理的功能,不妨试试管道模式。从简单的数据处理开始,慢慢你就会发现它的妙处。

公众号:蓝海资料掘金营,微信deep3321