周三的地铁上,小王本来想在脑子里过一遍今天要做的事。
旁边一位大哥戴着耳机打电话。耳机里对方说什么,别人听不见;大哥自己的声音,却一句一句落在外面。声音不算大,也没有嚷,可就是不停。
你刚把那件事想到一半,旁边又接上一句;刚想不去留意,下一句又钻进来了。最磨人的不是哪一句特别响,而是你拿不准这通电话还要多久。
小王没说什么,站起来,往车厢另一头挪了挪。距离拉开,思绪才慢慢接了回去。
耳机收住了电话另一端的声音,收不住说话人的声音
这让他想起之前的另一次经历。
一位老大爷在地铁里外放抖音,声音大得多,短视频一段接一段。小王一直在忍,没吭声。后来旁边一位女士受不了,开口提醒:“师傅,麻烦您小点声。”
老大爷把音量调低了。可这份安静没持续多久,声音又大了起来。
她开口的那一下,小王挺感激那位女士。她说出了他一直没说出口的话。
两次被打扰,一次走开,一次有人提醒。中间还夹着很多人最熟悉的选项:忍。必要的时候,还可以找工作人员。
把这些处理方式排好,当前这一环接不住,就把问题交给下一环——这就是今天聊的责任链模式(Chain of Responsibility)。系列第十一篇。
01 所有应对,先挤在一个方法里
先把一次打扰抽象成三个条件:是否持续、能否移动、是否适合提醒。
最直白的代码,当然是按顺序判断:
record Disturbance(
boolean continuous,
boolean canMoveAway,
boolean canRemind
) {}
void respond(Disturbance d) {
if (!d.continuous()) {
System.out.println("声音很快停了,继续想自己的事");
} else if (d.canMoveAway()) {
System.out.println("起身换个位置");
} else if (d.canRemind()) {
System.out.println("提醒对方调低音量");
} else {
System.out.println("联系工作人员协助");
}
}
所有判断和处理,都装在 respond() 里
事情少的时候,这样写最省事,没必要看见 if 就想着重构。
可处理方式还会继续增加:戴上自己的耳机、换一节车厢、向乘务员反映……条件多了,处理动作也变复杂,这个方法就得知道越来越多本来跟它无关的细节。
责任链要解决的不是“消灭 if”。判断不会凭空消失,而是让每种处理方式各自管好自己的条件和动作,别再全堆在一个地方。
02 每种处理方式,只管自己这一环
先搭一个共同骨架:能处理就处理;处理不了,交给下一环。
abstract class DisturbanceHandler {
private DisturbanceHandler next;
DisturbanceHandler andThen(DisturbanceHandler next) {
this.next = next;
return next;
}
void handle(Disturbance d) {
if (!tryHandle(d) && next != null) {
next.handle(d); // 我接不住,继续往下传
}
}
abstract boolean tryHandle(Disturbance d);
}
骨架只负责传递,不管具体怎么处理
“先忍一下”和“起身远离”各自实现这一环:
class EndureHandler extends DisturbanceHandler {
boolean tryHandle(Disturbance d) {
if (d.continuous()) return false; // 持续不断,忍不是办法
System.out.println("声音很快停了,继续想自己的事");
return true;
}
}
class MoveAwayHandler extends DisturbanceHandler {
boolean tryHandle(Disturbance d) {
if (!d.canMoveAway()) return false;
System.out.println("起身换个位置");
return true;
}
}
每一环只判断自己接不接得住
ReminderHandler 和 StaffHandler 也一样:不适合提醒就往后传;前面的办法都没接住,最后联系工作人员。
注意,那些判断条件还在。只是 continuous() 归忍耐这一环关心,canMoveAway() 归移动这一环关心,新增一种处理方式时,不必再回原来的大方法里挤一个分支。新处理者类和组装处的连接仍然要写,省掉的是处理规则在一处越缠越紧,不是新增功能零改动。
03 把环串好,谁接住就停在谁那里
组装的时候,把顺序写清楚:
DisturbanceHandler chain = new EndureHandler();
chain.andThen(new MoveAwayHandler())
.andThen(new ReminderHandler())
.andThen(new StaffHandler());
// 电话持续不断,但车厢里有地方可以挪
chain.handle(new Disturbance(true, true, true));
// 输出:起身换个位置
// 外放持续不断,不方便移动,适合出声提醒
chain.handle(new Disturbance(true, false, true));
// 输出:提醒对方调低音量
调用方把事交给第一环,不必先指定最后由谁处理
第一个请求在“起身远离”这一环被接住,就不再往后传。第二个请求前两环都没接,到了“出声提醒”才停下。
处理不了就往下传,有人接住就可以停
这就是责任链的核心:让多个处理者都有机会处理同一个请求,发起处理时不必预先知道最终由谁接住。
当然,组装链条的人知道这条链长什么样。所谓解耦,不是把结构变成谁都不知道的秘密,而是让真正发起 handle() 的地方只跟入口打交道。
04 提醒过一次,不代表以后都安静
老大爷后来又把音量调大,不是责任链“失效”了。
提醒处理的是当前这一次打扰,不是给对方的手机永久上锁。声音再次变大,可以看成一个新的请求,重新寻找合适的处理方式。
这里有两处类比失真,得挑明。
第一,现实生活并不会自动排成一条链。那位女士不是小王转交过去的“下一环”,她是自己决定开口的。先离开还是先提醒,也没有适用于所有车厢的固定顺序。代码里的排列只是为了讲清模式,不是公共生活的标准答案。
第二,canRemind() 在代码里只是一个布尔值,现实里却没有什么方法能替你算出“现在开口是否合适”。距离、语气、现场情况,都得由人判断。不愿意或者不便开口,不代表你处理失败;换个位置、联系工作人员,也都是真实的处理办法。
还有一点很重要:责任链不保证一定有人接。
示例里放了 StaffHandler 兜底。如果实际代码里每一环都返回 false,最后一环后面又是 null,请求就可能悄悄掉出链条。是记录日志、抛出异常,还是给出默认结果,必须明确决定,别让问题传着传着就没了。
05 跟策略、命令、状态,分别差在哪儿
策略模式(Day 4)管“选哪个方案”:早餐可以自己做,也可以楼下买,选定一个方案来完成目标。责任链管“谁能接住这件事”:请求可以依次经过多个处理者,直到有人处理。
命令模式(Day 6)管“把要做什么装成对象”:便签上写清楚执行、撤销和摘要。责任链管“这张便签交给谁办”。一个把请求封装起来,一个决定请求在处理者之间怎么流动,两者可以一起用。
状态模式(Day 10)管“当前状态下怎么做”:还是同一个对象,状态不同,行为不同。责任链则是多个处理者都有机会接手,重点不在对象内部发生了什么状态迁移。
Web 开发里的过滤器链,是常见的责任链变体。请求依次经过多个 Filter,每一环可以处理请求、修改响应,也可以继续交给下一环。它并不要求“有一个人处理就立刻停止”,所以别把责任链死记成只能有一个处理者。
AI 可以很快生成一套 next 和 tryHandle(),但环节按什么顺序排、什么情况算处理完成、没人接时怎么办,仍然得由人定。
06 什么时候,别急着串链
固定只有一个处理者时,直接调用。 每次请求都明确交给同一个对象,再包一层 next,只是让读者多追一次跳转。
每一环都必须执行时,把语义说清楚。 如果日志、校验、审计一个都不能少,那更接近流水线或过滤器链。别一边要求全部执行,一边又写成“有人处理就停止”。
链别长到没人看得懂。 处理者太多、顺序藏在各个配置里,出了问题会很难追。必要时的日志、明确的兜底,比再抽象一个“万能链框架”更有用。
判断标准可以落在一句话上:**这个请求是否可能由多个处理者中的一个或多个处理,而发起方不该写死具体是谁?**是,责任链才有用武之地。
07 不必每次都靠自己硬扛
挪到车厢另一头以后,小王没再留意那通电话。他扶着吊环,把刚才断掉的思绪一点一点重新捡起来。
他也不必因为上一次没开口,就欠自己一次“勇敢”。公共场合里,每个人当下能做的选择不一样。能走开就走开,愿意且适合时说一句,必要时找工作人员。
跟前面十篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管选哪个做法,装饰器管外面加几层,命令管把请求做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住。
带走一句话:责任链 = 请求沿着处理者往下传,谁能处理谁接住,接不住就交给下一环。
不是所有打扰,都必须靠你一个人硬扛。
愿每一节车厢都安静一点,也愿每一次必要的提醒,都能被好好听见。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)——责任链模式的原始定义:解除请求发送者与接收者之间的耦合,让多个对象都有机会处理请求,并沿处理者链传递请求
- Refactoring Guru · Chain of Responsibility——责任链的结构、处理者传递方式,以及与装饰器等模式的结构差别
- Jakarta Servlet 官方 API · FilterChain——过滤器链如何依次调用后续过滤器,并最终到达目标资源