告别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 种场景
核心思想就一句:解耦请求发送者和接收者,让处理逻辑可插拔、可组合。
三、责任链长啥样?有图有真相
我们先看两个核心类:
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,防止子类破坏流程
五、不止小工具!主流框架如何用责任链解决大问题?
责任链绝不是玩具模式,它是构建高内聚、低耦合系统的核心武器之一。
来看看几个重量级选手是怎么玩的:
1. Spring Security 的认证授权
在 Spring Security 中,每个 HTTP 请求都要经过一连串的 Filter,比如:
UsernamePasswordAuthenticationFilter:处理表单登录BearerTokenAuthenticationFilter:处理 JWT TokenAnonymousAuthenticationFilter:兜底匿名用户
这些 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 篇。
如果你对这个系列感兴趣,记得关注我的博客更新哦!