管道模式:让数据处理像流水线一样优雅
管道模式,说白了就是把数据处理拆成一个个独立的步骤,像工厂流水线一样串起来。每个步骤只管自己的事,做完就传给下一个。我在做中间件框架时,这个模式帮了大忙。
为什么需要管道模式?
你想想看,如果一段数据处理逻辑全写在一个方法里,会是什么样子?
- 代码又长又臭,根本没法维护
- 想加个新功能,得改老代码
- 想复用某个处理步骤?没门
- 测试起来特别痛苦
管道模式就是来解决这些问题的。它把处理逻辑拆成一个个处理器,每个处理器只做一件事。你可以自由组合、灵活编排。
核心思想:每个处理器只负责一个职责,通过管道串联起来形成处理链。
三个核心角色
管道模式有三个关键角色,我一个个说。
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"
管道模式的核心流程图
下面这张图展示了数据在管道中的流动过程:
中间件链:管道的进阶玩法
管道模式还有一个变体叫中间件链。每个处理器不仅能处理数据,还能决定是否继续传递。这在 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流程 | 抽取、转换、加载各步骤独立 | ⭐⭐⭐⭐ |
| 编译管道 | 词法分析、语法分析、语义分析 | ⭐⭐⭐⭐ |
总结
管道模式的核心价值就四个字:职责分离。每个处理器只做一件事,通过管道串联起来完成复杂任务。我个人觉得,这个模式最大的好处是让代码变得特别好测试——每个处理器都可以单独测,测完再组装。
嗯,如果你正在做一个需要多步骤处理的功能,不妨试试管道模式。从简单的数据处理开始,慢慢你就会发现它的妙处。