当前位置:首页>java>告别if-else手抽筋式写法:一行代码串起10个校验规则?用责任链模式把校验逻辑变成“流水线”

告别if-else手抽筋式写法:一行代码串起10个校验规则?用责任链模式把校验逻辑变成“流水线”

  • 2026-09-23 17:26:03
告别if-else手抽筋式写法:一行代码串起10个校验规则?用责任链模式把校验逻辑变成“流水线”

告别if-else手抽筋式写法:一行代码串起10个校验规则?用责任链模式把校验逻辑变成“流水线”

一、你是不是也这样写过?

publicvoidregister(String username, String email){
if (username == null || username.trim().isEmpty()) {
thrownew IllegalArgumentException("用户名不能为空");
    }
if (username.length() < 4) {
thrownew IllegalArgumentException("用户名太短");
    }
if (email == null || !email.contains("@")) {
thrownew IllegalArgumentException("邮箱格式不对");
    }
if (!email.endsWith(".com")) {
thrownew IllegalArgumentException("只支持 .com 邮箱");
    }
// ... 后面还有 10 条业务规则
}

写完自己都怕——改一条规则,心惊胆战;加一个字段,血压飙升。

更惨的是,这些逻辑还散落在各个 service 里,复制粘贴成了日常操作…… 这哪是写代码?这是在“堆 if 地狱”里搬砖啊!😭

而且,一旦业务变化(比如“用户名现在允许中文了”),你得满世界找 if 语句改,稍有遗漏就埋下 bug。

可维护性?不存在的。

二、救星来了:责任链模式(Chain of Responsibility)

它是什么?

一句话:把多个处理器串成一条链,请求从头流到尾,谁该处理谁上,处理完还能决定是否继续传递。

来看看用 StringButler 怎么写:

String username = StringButler.of(input)
    .validate()
        .notBlank()
        .lengthBetween(4, 20)
        .matches("[a-zA-Z0-9_]+")
    .transform()
        .trim()
        .toLowerCase()
    .getValueOrThrow();

PS:StringButler 是我的一个开源项目,项目地址:https://github.com/iweidujiang/string-butler

一行链式调用,搞定验证 + 转换 + 异常抛出。 而且——每加一个规则,只需多写一个方法,不用动原有逻辑!

这背后的核心功臣,就是今天要聊的:责任链模式(Chain of Responsibility)。

别被名字吓到,它其实就是——把一堆处理者串成一条“流水线”,数据从头流到尾,谁该干活谁上。

这种模式最早由 GoF(《设计模式》四人组)提出,初衷是:“避免请求发送者与接收者耦合,让多个对象都有机会处理请求”。

翻译成人话:我不关心谁处理,我只管把任务扔进管道,能干的自己上,干不了就传下去。

最适合用它的 4 种场景

场景
例子
多级审批
请假:组长 → 经理 → HR
多规则校验
注册:非空 → 长度 → 格式 → 唯一性
事件冒泡
Web 点击事件、Netty 数据包处理
中间件过滤
日志 → 鉴权 → 限流 → 业务

核心思想就一句:解耦请求发送者和接收者,让处理逻辑可插拔、可组合。

三、责任链长啥样?有图有真相

我们先看两个核心类:

  • ValidationChain:验证责任链
  • TransformationChain:转换责任链

它们内部都维护了一个 规则列表(List),比如:

// ValidationChain.java(简化版)
publicclassValidationChain{
privatefinal List<ValidationRule> rules = new ArrayList<>();
privatefinal String value;

public ValidationChain addNotBlankRule(){
        rules.add(new NotBlankRule());
returnthis;
    }

public ValidationChain addLengthRule(int min, int max){
        rules.add(new LengthRule(min, max));
returnthis;
    }

publicbooleanvalidateAll(){
for (ValidationRule rule : rules) {
if (!rule.validate(value)) {
// 一旦失败,立即中断并抛出错误信息
thrownew ValidationException(rule.getErrorMessage());
            }
        }
returntrue;
    }
}

整个流程就像工厂流水线:

优点一目了然:

  • 解耦:每个规则只关心自己那点事,不知道其他规则的存在
  • 可插拔:想加就加,想删就删,像搭积木一样灵活
  • 顺序可控:先验证再转换,逻辑清晰无歧义
  • 中断机制:一旦失败,立刻停止,不浪费性能(fail-fast 原则)

更重要的是——测试变得超级简单!每个 ValidationRule 都可以独立单元测试,不需要 mock 整个上下文。

四、真实代码走一波:手写一个极简责任链(5 分钟上手)

先看最经典的“谁处理谁终止”模式:

// 1. 定义处理器基类
publicabstractclassHandler{
protected Handler next;

public Handler setNext(Handler next){
this.next = next;
return next;
    }

// 模板方法:控制流程
publicfinalvoidprocess(String user){
if (canHandle(user)) {
            handle(user);
return; // 处理成功,链终止
        }
if (next != null) {
            next.process(user); // 交给下一个
        } else {
            System.out.println("无人能处理: " + user);
        }
    }

protectedabstractbooleancanHandle(String user);
protectedabstractvoidhandle(String user);
}

// 2. 实现具体处理器
classAdminHandlerextendsHandler{
@OverrideprotectedbooleancanHandle(String user){
return"admin".equals(user);
    }
@Overrideprotectedvoidhandle(String user){
        System.out.println("管理员登录成功");
    }
}

classGuestHandlerextendsHandler{
@OverrideprotectedbooleancanHandle(String user){
return"guest".equals(user);
    }
@Overrideprotectedvoidhandle(String user){
        System.out.println("访客登录成功");
    }
}

// 3. 组装 & 使用
publicclassMain{
publicstaticvoidmain(String[] args){
        Handler chain = new AdminHandler();
        chain.setNext(new GuestHandler());

        chain.process("admin");  // 管理员登录成功
        chain.process("guest");  // 访客登录成功
        chain.process("hacker"); // 无人能处理: hacker
    }
}

关键设计:

  • process() 是 final,防止子类破坏流程
  • canHandle() 决定是否“认领”请求
  • 链的组装完全由外部控制,极度灵活

五、不止小工具!主流框架如何用责任链解决大问题?

责任链绝不是玩具模式,它是构建高内聚、低耦合系统的核心武器之一。

来看看几个重量级选手是怎么玩的:

1. Spring Security 的认证授权

在 Spring Security 中,每个 HTTP 请求都要经过一连串的 Filter,比如:

  • UsernamePasswordAuthenticationFilter:处理表单登录
  • BearerTokenAuthenticationFilter:处理 JWT Token
  • AnonymousAuthenticationFilter:兜底匿名用户

这些 Filter 组成一条 Filter Chain,请求依次通过,只要有一个 Filter 完成认证,后续 Filter 就跳过。

核心代码示意:

publicinterfaceFilter{
voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain);
}

publicclassUsernamePasswordAuthenticationFilterimplementsFilter{
@Override
publicvoiddoFilter(ServletRequest req, ServletResponse res, FilterChain chain){
if (isLoginRequest(req)) {
            authenticate(req); // 认证成功
        }
        chain.doFilter(req, res); // 交给下一个 Filter
    }
}

和 StringButler 的共通点:每个环节只做一件事,失败或成功都明确传递控制权。

2. Netty 的网络事件

Netty 的 ChannelPipeline 是责任链的教科书级实现。一个网络包进来,会依次经过:

  • ByteToMessageDecoder:字节解码
  • BusinessLogicHandler:业务处理
  • MessageToByteEncoder:结果编码

核心代码示意:

publicclassChannelPipeline{
privatefinal List<ChannelHandler> handlers = new ArrayList<>();

publicvoidfireChannelRead(Object msg){
for (ChannelHandler handler : handlers) {
            msg = handler.channelRead(msg);
if (msg == null) break; // 中断链
        }
    }
}

每个 Handler 处理完,把结果传给下一个——完全解耦,高度可插拔。

3. Java Servlet:老牌但经典

最早的 Java Web 应用就用 Filter 实现日志、权限、编码统一处理:

@WebFilter("/*")
publicclassLoggingFilterimplementsFilter{
publicvoiddoFilter(ServletRequest req, ServletResponse res, FilterChain chain){
long start = System.currentTimeMillis();
        chain.doFilter(req, res); // 放行
        log.info("Request took: " + (System.currentTimeMillis() - start) + "ms");
    }
}

责任链在这里实现了“横切关注点”的集中管理——这正是 AOP(面向切面编程)的思想雏形。

总结一下: 无论是 StringButler 的字符串校验,还是 Spring 的安全控制、Netty 的网络通信,责任链的本质都是:把复杂流程拆解为独立、有序、可中断的小步骤。 小项目用它提升可读性,大系统用它保障可扩展性——一招鲜,吃遍天。

六、责任链的两个常见误区

误区1:链太长影响性能?

其实不然!StringButler 的链通常只有 2~5 个规则,且失败即停(fail-fast),平均执行次数远小于 if-else 堆叠。

而且,规则对象通常是无状态的单例(或轻量实例),创建开销极小。

误区2:规则之间有依赖怎么办?

比如“必须先 trim 再验证长度”?

答案是:分阶段!

StringButler 就是这么干的:先跑完整个验证链(基于原始值),再跑转换链(生成新值),天然隔离阶段。

如果真有强依赖(比如 A 规则的结果影响 B 规则的判断),那可能就不适合用标准责任链了。这时候可以:

  • 在链中传递一个 上下文对象(Context),各环节读写共享状态
  • 或者考虑 状态模式 / 策略组合

但记住:90% 的校验/转换场景,根本不需要跨规则依赖——保持规则原子性,才是王道。

七、给你的项目提个建议

如果你的业务里有类似场景:

  • 多步骤审批(初审 → 复审 → 终审)
  • 多条件校验(格式 → 业务唯一性 → 风控)
  • 多阶段数据处理(清洗 → 转换 → 加密)

不妨试试责任链!哪怕不用 StringButler,也可以自己撸一个简易版:

@FunctionalInterface
publicinterfaceRule{
booleanapply(String input);
}

publicclassRuleChain{
privatefinal List<Rule> rules = new ArrayList<>();

public RuleChain add(Rule rule){
        rules.add(rule);
returnthis;
    }

publicvoidexecute(String input){
for (Rule rule : rules) {
if (!rule.apply(input)) {
thrownew RuntimeException("规则校验失败");
            }
        }
    }
}

// 使用
new RuleChain()
    .add(s -> s != null && !s.isEmpty())
    .add(s -> s.length() >= 4)
    .execute(username);

几行代码,告别 if 地狱,拥抱声明式编程!

八、结语:好设计,是让复杂变简单

StringButler 不是一个炫技的玩具,而是一次对“如何写出可维护、可扩展、可读性强的工具代码”的认真尝试。

责任链模式,正是其中关键一环——它让验证不再是负担,而是一种声明式的表达。

写代码,不是为了机器能跑,而是为了让人类(包括未来的你)能看懂、能改、敢改。

当你下次面对一堆 if-else 时,不妨问自己一句:

“这段逻辑,能不能变成一条流水线?”


本文是《StringButler设计哲学与实战》系列的第 3 篇。

如果你对这个系列感兴趣,记得关注我的博客更新哦!

最新文章

随机文章