设计模式与生活⑪:地铁里被打扰,别总靠自己硬扛

0 阅读9分钟

周三的地铁上,小王本来想在脑子里过一遍今天要做的事。

旁边一位大哥戴着耳机打电话。耳机里对方说什么,别人听不见;大哥自己的声音,却一句一句落在外面。声音不算大,也没有嚷,可就是不停。

你刚把那件事想到一半,旁边又接上一句;刚想不去留意,下一句又钻进来了。最磨人的不是哪一句特别响,而是你拿不准这通电话还要多久。

小王没说什么,站起来,往车厢另一头挪了挪。距离拉开,思绪才慢慢接了回去。

scene-subway.jpg

耳机收住了电话另一端的声音,收不住说话人的声音

这让他想起之前的另一次经历。

一位老大爷在地铁里外放抖音,声音大得多,短视频一段接一段。小王一直在忍,没吭声。后来旁边一位女士受不了,开口提醒:“师傅,麻烦您小点声。”

老大爷把音量调低了。可这份安静没持续多久,声音又大了起来。

她开口的那一下,小王挺感激那位女士。她说出了他一直没说出口的话。

两次被打扰,一次走开,一次有人提醒。中间还夹着很多人最熟悉的选项:忍。必要的时候,还可以找工作人员。

把这些处理方式排好,当前这一环接不住,就把问题交给下一环——这就是今天聊的责任链模式(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));
// 输出:提醒对方调低音量

调用方把事交给第一环,不必先指定最后由谁处理

第一个请求在“起身远离”这一环被接住,就不再往后传。第二个请求前两环都没接,到了“出声提醒”才停下。

chain-of-responsibility.png

处理不了就往下传,有人接住就可以停

这就是责任链的核心:让多个处理者都有机会处理同一个请求,发起处理时不必预先知道最终由谁接住。

当然,组装链条的人知道这条链长什么样。所谓解耦,不是把结构变成谁都不知道的秘密,而是让真正发起 handle() 的地方只跟入口打交道。

04 提醒过一次,不代表以后都安静

老大爷后来又把音量调大,不是责任链“失效”了。

提醒处理的是当前这一次打扰,不是给对方的手机永久上锁。声音再次变大,可以看成一个新的请求,重新寻找合适的处理方式。

这里有两处类比失真,得挑明。

第一,现实生活并不会自动排成一条链。那位女士不是小王转交过去的“下一环”,她是自己决定开口的。先离开还是先提醒,也没有适用于所有车厢的固定顺序。代码里的排列只是为了讲清模式,不是公共生活的标准答案。

第二,canRemind() 在代码里只是一个布尔值,现实里却没有什么方法能替你算出“现在开口是否合适”。距离、语气、现场情况,都得由人判断。不愿意或者不便开口,不代表你处理失败;换个位置、联系工作人员,也都是真实的处理办法。

还有一点很重要:责任链不保证一定有人接。

示例里放了 StaffHandler 兜底。如果实际代码里每一环都返回 false,最后一环后面又是 null,请求就可能悄悄掉出链条。是记录日志、抛出异常,还是给出默认结果,必须明确决定,别让问题传着传着就没了。

05 跟策略、命令、状态,分别差在哪儿

策略模式(Day 4)管“选哪个方案”:早餐可以自己做,也可以楼下买,选定一个方案来完成目标。责任链管“谁能接住这件事”:请求可以依次经过多个处理者,直到有人处理。

命令模式(Day 6)管“把要做什么装成对象”:便签上写清楚执行、撤销和摘要。责任链管“这张便签交给谁办”。一个把请求封装起来,一个决定请求在处理者之间怎么流动,两者可以一起用。

状态模式(Day 10)管“当前状态下怎么做”:还是同一个对象,状态不同,行为不同。责任链则是多个处理者都有机会接手,重点不在对象内部发生了什么状态迁移。

Web 开发里的过滤器链,是常见的责任链变体。请求依次经过多个 Filter,每一环可以处理请求、修改响应,也可以继续交给下一环。它并不要求“有一个人处理就立刻停止”,所以别把责任链死记成只能有一个处理者。

AI 可以很快生成一套 next 和 tryHandle(),但环节按什么顺序排、什么情况算处理完成、没人接时怎么办,仍然得由人定。

06 什么时候,别急着串链

固定只有一个处理者时,直接调用。 每次请求都明确交给同一个对象,再包一层 next,只是让读者多追一次跳转。

每一环都必须执行时,把语义说清楚。 如果日志、校验、审计一个都不能少,那更接近流水线或过滤器链。别一边要求全部执行,一边又写成“有人处理就停止”。

链别长到没人看得懂。 处理者太多、顺序藏在各个配置里,出了问题会很难追。必要时的日志、明确的兜底,比再抽象一个“万能链框架”更有用。

判断标准可以落在一句话上:**这个请求是否可能由多个处理者中的一个或多个处理,而发起方不该写死具体是谁?**是,责任链才有用武之地。

07 不必每次都靠自己硬扛

挪到车厢另一头以后,小王没再留意那通电话。他扶着吊环,把刚才断掉的思绪一点一点重新捡起来。

他也不必因为上一次没开口,就欠自己一次“勇敢”。公共场合里,每个人当下能做的选择不一样。能走开就走开,愿意且适合时说一句,必要时找工作人员。

跟前面十篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管选哪个做法,装饰器管外面加几层,命令管把请求做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住。

带走一句话:责任链 = 请求沿着处理者往下传,谁能处理谁接住,接不住就交给下一环。

不是所有打扰,都必须靠你一个人硬扛。

愿每一节车厢都安静一点,也愿每一次必要的提醒,都能被好好听见。


参考资料