[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-17":26,"\u002Farticles\u002Fdesign-pattern-17\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-17\u002Fadjacent":72,"\u002Fcategories":84,"render-design-pattern-17":110},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113932208883372034","design-pattern-17","设计模式修炼之路 ⑰ 责任链模式——请求沿链条传递","客户端只认识链头、节点自治三段式、纯链与管线式、从 Servlet Filter 到 Netty Pipeline","2113897628365230081",1,3708,5950,15,"2024-07-23T20:24:49","> 行为型第四站。\n\n上一篇结尾留了个场景：报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。\n\n这一篇就把这套\"击鼓传花\"讲透——责任链模式，审批流的经典实现，也是各大框架的骨架模式。\n\n## 一、责任链模式是什么\n\nGoF 的原始定义：\n\n> Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.\n> （让多个对象都有机会处理请求，避免请求的发送者和接收者耦合。把接收对象串成一条链，让请求沿着链传递，直到有对象处理它为止。）\n\n翻译成人话：**把一串处理者连成链，请求进来只递给第一个；每个处理者自己决定——在我手里就处理，不在我手里就传给下家。**\n\n三个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Handler（抽象处理者）\u003C\u002Ftd>\u003Ctd>定义处理接口 + 持有下家的引用\u003C\u002Ftd>\u003Ctd>Approver\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteHandler（具体处理者）\u003C\u002Ftd>\u003Ctd>自己的权限范围内处理，范围外转交下家\u003C\u002Ftd>\u003Ctd>组长\u002F经理\u002F总监\u002F财务总监\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Client（客户端）\u003C\u002Ftd>\u003Ctd>组链 + 把请求交给链头\u003C\u002Ftd>\u003Ctd>ChainTest\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n类比就是报销审批本身：你（Client）只把单子递给直属组长（链头），组长翻一下额度——300 块，自己就签了；80 万？签不动，转给经理。**你从不需要知道整条审批线长什么样，你只认识你组长。**\n\n和前两篇串起来看，行为型已经凑齐了\"分派三部曲\"：⑮ 策略解决\"选哪个算法\"，⑯ 观察者解决\"通知哪些下游\"，⑰ 责任链解决\"请求谁接盘\"——前两个的决策权在调用方\u002F名单，这一个的决策权下放到每个节点自己手里。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **发送者只认识链头**。客户端不知道有几个处理者、怎么路由，耦合度降到最低。\n2. **节点自治**。每个处理者只关心\"我的额度是多少、批还是转\"，节点之间互不认识——除了自己的下家。\n3. **动态组链**。链条是运行时拼的：加一级审批、临时抽掉一个环节、换个顺序，都是改一行组装代码的事，节点类不动。\n4. **符合开闭原则**。新加一个 VP 审批层 = 写一个类 + 组链时插进去。\n\n**缺点：**\n\n1. **请求可能没人处理**。链走到头还是没人接，请求就\"落空\"了——本例 200 万报销单就走到了链尾。必须设计好兜底（驳回、默认处理、或者日志告警），否则请求无声消失，是生产事故级 bug。\n2. **调试费劲**。一个请求在链上跳来跳去，出问题时要在每个节点打断点、看日志，不如一个 if-else 直观。\n3. **性能损耗**。权限明明在总监手里，请求还是要从组长、经理白跑两站。链越长、命中越靠后，浪费越多。\n4. **顺序敏感**。组链的人把总监排在组长前面，整条链的逻辑就乱了。链条的正确性靠组装代码保证，编译器帮不了你。\n\n## 三、什么时候用\n\n判断标准：**同一个请求，有多个候选处理者，该谁处理要由\"请求内容 + 处理者能力\"在运行时决定，且发送者不该知道路由规则。**\n\n- **层级审批**：报销、请假、合同会签——本文场景\n- **规则拦截**：风控系统里\"黑名单→限频→额度\"层层筛查，谁先拦住谁处理\n- **请求预处理管线**：编码、鉴权、日志、防攻击……每个环节处理自己的部分再放行（见第五节 Servlet Filter）\n- **事件传递**：GUI 里的事件冒泡，子控件不处理就往父控件传\n\n反面对照：处理者唯一且已知——直接调用；处理者之间要全跑一遍——那是观察者（⑯）的广播；需要所有分支并列可选——那是策略（⑮）。\n\n## 四、实现方式：把路由权下放给节点\n\n### 4.1 反面教材：提交人自己路由\n\n```java\npublic class BadExpenseSystem {\n\n    public void submit(double amount) {\n        if (amount \u003C= 1000) {\n            System.out.println(\"  [组长] 批准报销 \" + amount + \" 元\");\n        } else if (amount \u003C= 10000) {\n            System.out.println(\"  [经理] 批准报销 \" + amount + \" 元\");\n        } else if (amount \u003C= 100000) {\n            System.out.println(\"  [总监] 批准报销 \" + amount + \" 元\");\n        } else if (amount \u003C= 1000000) {\n            System.out.println(\"  [财务总监] 批准报销 \" + amount + \" 元\");\n        } else {\n            System.out.println(\"  [无人能批] 金额过大，提交董事会讨论\");\n        }\n        \u002F\u002F 公司新设一个 VP 审批层？各级额度调整？\n        \u002F\u002F 这里全得改，而且所有要审批的地方都得复制一遍这套路由规则\n    }\n}\n```\n\n又是 if-else 巨无霸？看着像 ⑮ 的病，其实病灶不同：⑮ 的分支是**并列的算法**，这里的分支是**层级路由**——它的问题不是 if-else 本身，而是\"路由规则\"被焊死在了提交人这边。公司调整额度、新设层级，所有提交审批的代码全要改一遍。\n\n### 4.2 正解：Handler 拿着下家引用，各自为政\n\n第一步，抽象处理者——核心就两个字段一个方法：\n\n```java\npublic abstract class Approver {\n\n    protected final String name;\n    protected Approver next;          \u002F\u002F 下一个处理者\n\n    public Approver(String name) {\n        this.name = name;\n    }\n\n    \u002F\u002F 组链：返回下一个节点，方便链式调用\n    public Approver setNext(Approver next) {\n        this.next = next;\n        return next;\n    }\n\n    \u002F\u002F 处理请求：自己处理，或者传给下家\n    public abstract void approve(double amount);\n}\n```\n\n第二步，每个具体处理者实现同一套决策逻辑——权限内处理，权限外转交：\n\n```java\npublic class LeaderApprover extends Approver {\n\n    public LeaderApprover(String name) {\n        super(name);\n    }\n\n    @Override\n    public void approve(double amount) {\n        if (amount \u003C= 1000) {\n            System.out.println(\"  [组长\" + name + \"] 批准报销 \" + amount + \" 元\");\n        } else if (next != null) {\n            System.out.println(\"  [组长\" + name + \"] \" + amount + \" 元超出权限，转交下一级\");\n            next.approve(amount);\n        } else {\n            System.out.println(\"  [组长\" + name + \"] 我是链条最后一环，无人能批，驳回\");\n        }\n    }\n}\n```\n\nManagerApprover（≤1 万）、DirectorApprover（≤10 万）、CfoApprover（≤100 万）结构完全相同，只是阈值和称谓不同。\n\n第三步，客户端组链、递交：\n\n```java\npublic class ChainTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：提交人自己路由 ==========\");\n        BadExpenseSystem bad = new BadExpenseSystem();\n        bad.submit(300);\n        bad.submit(8000);\n        bad.submit(50000);\n        bad.submit(500000);\n        bad.submit(2000000);\n\n        System.out.println();\n        System.out.println(\"========== 正解：责任链模式 ==========\");\n        Approver chain = new LeaderApprover(\"张组长\");\n        chain.setNext(new ManagerApprover(\"李经理\"))\n             .setNext(new DirectorApprover(\"王总监\"))\n             .setNext(new CfoApprover(\"赵财务总监\"));\n\n        \u002F\u002F 客户端只认识链条的第一个节点\n        chain.approve(300);\n        chain.approve(8000);\n        chain.approve(50000);\n        chain.approve(500000);\n        chain.approve(2000000);\n    }\n}\n```\n\n运行输出：\n\n```text\n========== 反面教材：提交人自己路由 ==========\n  [组长] 批准报销 300.0 元\n  [经理] 批准报销 8000.0 元\n  [总监] 批准报销 50000.0 元\n  [财务总监] 批准报销 500000.0 元\n  [无人能批] 金额过大，提交董事会讨论\n\n========== 正解：责任链模式 ==========\n  [组长张组长] 批准报销 300.0 元\n  [组长张组长] 8000.0 元超出权限，转交下一级\n  [经理李经理] 批准报销 8000.0 元\n  [组长张组长] 50000.0 元超出权限，转交下一级\n  [经理李经理] 50000.0 元超出权限，转交下一级\n  [总监王总监] 批准报销 50000.0 元\n  [组长张组长] 500000.0 元超出权限，转交下一级\n  [经理李经理] 500000.0 元超出权限，转交下一级\n  [总监王总监] 500000.0 元超出权限，转交下一级\n  [财务总监赵财务总监] 批准报销 500000.0 元\n  [组长张组长] 2000000.0 元超出权限，转交下一级\n  [经理李经理] 2000000.0 元超出权限，转交下一级\n  [总监王总监] 2000000.0 元超出权限，转交下一级\n  [财务总监赵财务总监] 我是链条最后一环，无人能批，提交董事会讨论\n```\n\n### 4.3 三处细节咂摸一下\n\n**细节一：客户端自始至终只调 `chain.approve(...)`，只认识链头。**金额是 300 还是 200 万，客户端根本不关心——路由决策完全下放给了节点。对比反面教材里提交人背着一整套路由规则，这就是责任链的本质收益。\n\n**细节二：每个节点的三段式判断——\"我的额度内 \u002F 我有下家 \u002F 我到头了\"。**`amount \u003C= 1000` 是本职，`next != null` 是转交，`else` 是兜底。第三段最容易被新手漏掉：链尾没有兜底，请求就无声落空，正是第二节缺点一说的生产事故源。\n\n**细节三：`setNext` 返回下家引用，组链才能写成一行流式调用。**`chain.setNext(A).setNext(B).setNext(C)`——不是炫技，是让\"链条的形状\"在组装代码里一目了然，一眼看出层级顺序。第五节 Netty 的 Pipeline 组装 API 就是这个味道。\n\n### 4.4 纯责任链 vs 管线式责任链\n\n本文是教科书上的**纯责任链**：请求被某一个节点接住就结束了，后面的节点不再参与。\n\n但框架世界里更常见的是**管线式**：每个节点都处理一下（加工、校验、增强），处理完继续放行，请求穿过整条链——Servlet Filter、Spring Interceptor、Netty Pipeline 全是这种。两者的骨架代码几乎一样，区别只在节点是\"拦截\"还是\"放行\"。第五节展开看实物。\n\n另外你可能注意到，四个具体处理者的\"三段式\"结构高度重复——这正是 ⑭ 模板模式的用武之地：把\"判断权限→处理→转交\"的骨架提到父类做成 final 方法，子类只提供阈值。责任链管\"链怎么传\"，模板管\"节点内部长什么样\"，两模式叠加毫不冲突。\n\n## 五、在框架里找它\n\n责任链是当之无愧的\"框架骨架模式\"，你每天写的每个请求都在链上跑。\n\n**Servlet Filter——管线式责任链的祖师爷。**\n\n```java\npublic class LogFilter implements Filter {\n    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)\n            throws IOException, ServletException {\n        System.out.println(\"请求进来：先记日志\");\n        chain.doFilter(req, resp);        \u002F\u002F 放行给下一个 Filter\n        System.out.println(\"响应回去：补一条访问记录\");\n    }\n}\n```\n\n注意 `FilterChain` 这个参数——框架把\"下家\"包装成了链条对象递给节点。放行前是自己的一段前处理，放行后还能做后处理：请求沿链进、响应沿链出，一个 Filter 就是一节双向管道。编码、鉴权、日志、防 XSS……Tomcat 里的每个请求都要穿过这条链。\n\n**Spring MVC Interceptor。**同款骨架换了个名字：`preHandle` 返回 true 放行、返回 false 拦停，`postHandle`、`afterCompletion` 负责回程处理。登录校验、接口限流、租户路由，实现类排成一条链。\n\n**Netty Pipeline——把责任链做成了骨架级架构。**每个 Channel 挂一条 ChannelPipeline，编解码器、心跳处理器、业务处理器都是链上的 ChannelHandler，靠 `ctx.fireChannelRead(msg)` 一节节往下传（想拦截就不调用 fire）。Netty 用一条责任链撑起了整个网络事件处理模型——责任链能玩到多大，看 Netty 就知道。\n\n**OkHttp Interceptor。**RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor……一次 HTTP 请求的\"重试→补头→查缓存→建连接\"全在链上，还开放出口让你加自定义拦截器（比了个耶：透明压缩、统一加签都是这么实现的）。Retrofit、 Glide 的原理也都有它的影子。\n\n一句话总结这一节：**凡是\"请求要经过一串加工站\"的地方，框架作者的第一反应都是责任链。**\n\n## 六、分派三部曲合订：策略、观察者、责任链\n\n连讲三篇的\"分派\"问题，最后合上账本对比一下：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>策略模式 ⑮\u003C\u002Fth>\u003Cth>观察者模式 ⑯\u003C\u002Fth>\u003Cth>责任链模式 ⑰\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>请求给谁\u003C\u002Ftd>\u003Ctd>调用方**指定一个**\u003C\u002Ftd>\u003Ctd>**广播给全部**\u003C\u002Ftd>\u003Ctd>沿链试探，**通常一个接盘**\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>决策权\u003C\u002Ftd>\u003Ctd>调用方\u003C\u002Ftd>\u003Ctd>名单上所有人（都执行）\u003C\u002Ftd>\u003Ctd>每个节点自己\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>处理者数量\u003C\u002Ftd>\u003Ctd>1\u003C\u002Ftd>\u003Ctd>N（全执行）\u003C\u002Ftd>\u003Ctd>0 或 1（纯链）\u002F N（管线式）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>耦合方向\u003C\u002Ftd>\u003Ctd>Context 依赖接口\u003C\u002Ftd>\u003Ctd>Subject 依赖接口\u003C\u002Ftd>\u003Ctd>节点只依赖下家\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>典型场景\u003C\u002Ftd>\u003Ctd>促销算法、支付渠道\u003C\u002Ftd>\u003Ctd>事件广播\u003C\u002Ftd>\u003Ctd>审批流、过滤器\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n面试遇到\"这三个模式有什么区别\"，照着这张表说，再把本文三段代码场景各举一例，基本满分。\n\n## 写在最后\n\n责任链模式一句话总结：**一串处理者连成链，请求只递给链头；每个节点要么接盘，要么转交，要么兜底喊一嗓子。**\n\n下一篇 ⑱ 命令模式——责任链传的\"请求\"始终只是个参数，命令模式更进一步：**把请求本身变成对象**。编辑器的撤销键、任务队列里的一个个任务、可编排的宏录制……\"把干什么打包成对象\"之后，排队、撤销、日志、重放全是白送的。命令模式，下一篇见。","\u003Cblockquote>\n \u003Cp>行为型第四站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾留了个场景：报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。\u003C\u002Fp>\n\u003Cp>这一篇就把这套\"击鼓传花\"讲透——责任链模式，审批流的经典实现，也是各大框架的骨架模式。\u003C\u002Fp>\n\u003Ch2>一、责任链模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. （让多个对象都有机会处理请求，避免请求的发送者和接收者耦合。把接收对象串成一条链，让请求沿着链传递，直到有对象处理它为止。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把一串处理者连成链，请求进来只递给第一个；每个处理者自己决定——在我手里就处理，不在我手里就传给下家。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Handler（抽象处理者）\u003C\u002Ftd>\n   \u003Ctd>定义处理接口 + 持有下家的引用\u003C\u002Ftd>\n   \u003Ctd>Approver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteHandler（具体处理者）\u003C\u002Ftd>\n   \u003Ctd>自己的权限范围内处理，范围外转交下家\u003C\u002Ftd>\n   \u003Ctd>组长\u002F经理\u002F总监\u002F财务总监\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Client（客户端）\u003C\u002Ftd>\n   \u003Ctd>组链 + 把请求交给链头\u003C\u002Ftd>\n   \u003Ctd>ChainTest\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>类比就是报销审批本身：你（Client）只把单子递给直属组长（链头），组长翻一下额度——300 块，自己就签了；80 万？签不动，转给经理。\u003Cstrong>你从不需要知道整条审批线长什么样，你只认识你组长。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>和前两篇串起来看，行为型已经凑齐了\"分派三部曲\"：⑮ 策略解决\"选哪个算法\"，⑯ 观察者解决\"通知哪些下游\"，⑰ 责任链解决\"请求谁接盘\"——前两个的决策权在调用方\u002F名单，这一个的决策权下放到每个节点自己手里。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>发送者只认识链头\u003C\u002Fstrong>。客户端不知道有几个处理者、怎么路由，耦合度降到最低。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>节点自治\u003C\u002Fstrong>。每个处理者只关心\"我的额度是多少、批还是转\"，节点之间互不认识——除了自己的下家。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态组链\u003C\u002Fstrong>。链条是运行时拼的：加一级审批、临时抽掉一个环节、换个顺序，都是改一行组装代码的事，节点类不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>符合开闭原则\u003C\u002Fstrong>。新加一个 VP 审批层 = 写一个类 + 组链时插进去。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>请求可能没人处理\u003C\u002Fstrong>。链走到头还是没人接，请求就\"落空\"了——本例 200 万报销单就走到了链尾。必须设计好兜底（驳回、默认处理、或者日志告警），否则请求无声消失，是生产事故级 bug。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>调试费劲\u003C\u002Fstrong>。一个请求在链上跳来跳去，出问题时要在每个节点打断点、看日志，不如一个 if-else 直观。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>性能损耗\u003C\u002Fstrong>。权限明明在总监手里，请求还是要从组长、经理白跑两站。链越长、命中越靠后，浪费越多。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>顺序敏感\u003C\u002Fstrong>。组链的人把总监排在组长前面，整条链的逻辑就乱了。链条的正确性靠组装代码保证，编译器帮不了你。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>同一个请求，有多个候选处理者，该谁处理要由\"请求内容 + 处理者能力\"在运行时决定，且发送者不该知道路由规则。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>层级审批\u003C\u002Fstrong>：报销、请假、合同会签——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>规则拦截\u003C\u002Fstrong>：风控系统里\"黑名单→限频→额度\"层层筛查，谁先拦住谁处理\u003C\u002Fli>\n \u003Cli>\u003Cstrong>请求预处理管线\u003C\u002Fstrong>：编码、鉴权、日志、防攻击……每个环节处理自己的部分再放行（见第五节 Servlet Filter）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>事件传递\u003C\u002Fstrong>：GUI 里的事件冒泡，子控件不处理就往父控件传\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：处理者唯一且已知——直接调用；处理者之间要全跑一遍——那是观察者（⑯）的广播；需要所有分支并列可选——那是策略（⑮）。\u003C\u002Fp>\n\u003Ch2>四、实现方式：把路由权下放给节点\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：提交人自己路由\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadExpenseSystem {\n\n    public void submit(double amount) {\n        if (amount &lt;= 1000) {\n            System.out.println(\"  [组长] 批准报销 \" + amount + \" 元\");\n        } else if (amount &lt;= 10000) {\n            System.out.println(\"  [经理] 批准报销 \" + amount + \" 元\");\n        } else if (amount &lt;= 100000) {\n            System.out.println(\"  [总监] 批准报销 \" + amount + \" 元\");\n        } else if (amount &lt;= 1000000) {\n            System.out.println(\"  [财务总监] 批准报销 \" + amount + \" 元\");\n        } else {\n            System.out.println(\"  [无人能批] 金额过大，提交董事会讨论\");\n        }\n        \u002F\u002F 公司新设一个 VP 审批层？各级额度调整？\n        \u002F\u002F 这里全得改，而且所有要审批的地方都得复制一遍这套路由规则\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>又是 if-else 巨无霸？看着像 ⑮ 的病，其实病灶不同：⑮ 的分支是\u003Cstrong>并列的算法\u003C\u002Fstrong>，这里的分支是\u003Cstrong>层级路由\u003C\u002Fstrong>——它的问题不是 if-else 本身，而是\"路由规则\"被焊死在了提交人这边。公司调整额度、新设层级，所有提交审批的代码全要改一遍。\u003C\u002Fp>\n\u003Ch3>4.2 正解：Handler 拿着下家引用，各自为政\u003C\u002Fh3>\n\u003Cp>第一步，抽象处理者——核心就两个字段一个方法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public abstract class Approver {\n\n    protected final String name;\n    protected Approver next;          \u002F\u002F 下一个处理者\n\n    public Approver(String name) {\n        this.name = name;\n    }\n\n    \u002F\u002F 组链：返回下一个节点，方便链式调用\n    public Approver setNext(Approver next) {\n        this.next = next;\n        return next;\n    }\n\n    \u002F\u002F 处理请求：自己处理，或者传给下家\n    public abstract void approve(double amount);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，每个具体处理者实现同一套决策逻辑——权限内处理，权限外转交：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class LeaderApprover extends Approver {\n\n    public LeaderApprover(String name) {\n        super(name);\n    }\n\n    @Override\n    public void approve(double amount) {\n        if (amount &lt;= 1000) {\n            System.out.println(\"  [组长\" + name + \"] 批准报销 \" + amount + \" 元\");\n        } else if (next != null) {\n            System.out.println(\"  [组长\" + name + \"] \" + amount + \" 元超出权限，转交下一级\");\n            next.approve(amount);\n        } else {\n            System.out.println(\"  [组长\" + name + \"] 我是链条最后一环，无人能批，驳回\");\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>ManagerApprover（≤1 万）、DirectorApprover（≤10 万）、CfoApprover（≤100 万）结构完全相同，只是阈值和称谓不同。\u003C\u002Fp>\n\u003Cp>第三步，客户端组链、递交：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class ChainTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：提交人自己路由 ==========\");\n        BadExpenseSystem bad = new BadExpenseSystem();\n        bad.submit(300);\n        bad.submit(8000);\n        bad.submit(50000);\n        bad.submit(500000);\n        bad.submit(2000000);\n\n        System.out.println();\n        System.out.println(\"========== 正解：责任链模式 ==========\");\n        Approver chain = new LeaderApprover(\"张组长\");\n        chain.setNext(new ManagerApprover(\"李经理\"))\n             .setNext(new DirectorApprover(\"王总监\"))\n             .setNext(new CfoApprover(\"赵财务总监\"));\n\n        \u002F\u002F 客户端只认识链条的第一个节点\n        chain.approve(300);\n        chain.approve(8000);\n        chain.approve(50000);\n        chain.approve(500000);\n        chain.approve(2000000);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">========== 反面教材：提交人自己路由 ==========\n  [组长] 批准报销 300.0 元\n  [经理] 批准报销 8000.0 元\n  [总监] 批准报销 50000.0 元\n  [财务总监] 批准报销 500000.0 元\n  [无人能批] 金额过大，提交董事会讨论\n\n========== 正解：责任链模式 ==========\n  [组长张组长] 批准报销 300.0 元\n  [组长张组长] 8000.0 元超出权限，转交下一级\n  [经理李经理] 批准报销 8000.0 元\n  [组长张组长] 50000.0 元超出权限，转交下一级\n  [经理李经理] 50000.0 元超出权限，转交下一级\n  [总监王总监] 批准报销 50000.0 元\n  [组长张组长] 500000.0 元超出权限，转交下一级\n  [经理李经理] 500000.0 元超出权限，转交下一级\n  [总监王总监] 500000.0 元超出权限，转交下一级\n  [财务总监赵财务总监] 批准报销 500000.0 元\n  [组长张组长] 2000000.0 元超出权限，转交下一级\n  [经理李经理] 2000000.0 元超出权限，转交下一级\n  [总监王总监] 2000000.0 元超出权限，转交下一级\n  [财务总监赵财务总监] 我是链条最后一环，无人能批，提交董事会讨论\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：客户端自始至终只调 \u003Ccode>chain.approve(...)\u003C\u002Fcode>，只认识链头。**金额是 300 还是 200 万，客户端根本不关心——路由决策完全下放给了节点。对比反面教材里提交人背着一整套路由规则，这就是责任链的本质收益。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：每个节点的三段式判断——\"我的额度内 \u002F 我有下家 \u002F 我到头了\"。\u003C\u002Fstrong>\u003Ccode>amount &lt;= 1000\u003C\u002Fcode> 是本职，\u003Ccode>next != null\u003C\u002Fcode> 是转交，\u003Ccode>else\u003C\u002Fcode> 是兜底。第三段最容易被新手漏掉：链尾没有兜底，请求就无声落空，正是第二节缺点一说的生产事故源。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：\u003Ccode>setNext\u003C\u002Fcode> 返回下家引用，组链才能写成一行流式调用。\u003C\u002Fstrong>\u003Ccode>chain.setNext(A).setNext(B).setNext(C)\u003C\u002Fcode>——不是炫技，是让\"链条的形状\"在组装代码里一目了然，一眼看出层级顺序。第五节 Netty 的 Pipeline 组装 API 就是这个味道。\u003C\u002Fp>\n\u003Ch3>4.4 纯责任链 vs 管线式责任链\u003C\u002Fh3>\n\u003Cp>本文是教科书上的\u003Cstrong>纯责任链\u003C\u002Fstrong>：请求被某一个节点接住就结束了，后面的节点不再参与。\u003C\u002Fp>\n\u003Cp>但框架世界里更常见的是\u003Cstrong>管线式\u003C\u002Fstrong>：每个节点都处理一下（加工、校验、增强），处理完继续放行，请求穿过整条链——Servlet Filter、Spring Interceptor、Netty Pipeline 全是这种。两者的骨架代码几乎一样，区别只在节点是\"拦截\"还是\"放行\"。第五节展开看实物。\u003C\u002Fp>\n\u003Cp>另外你可能注意到，四个具体处理者的\"三段式\"结构高度重复——这正是 ⑭ 模板模式的用武之地：把\"判断权限→处理→转交\"的骨架提到父类做成 final 方法，子类只提供阈值。责任链管\"链怎么传\"，模板管\"节点内部长什么样\"，两模式叠加毫不冲突。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Cp>责任链是当之无愧的\"框架骨架模式\"，你每天写的每个请求都在链上跑。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Servlet Filter——管线式责任链的祖师爷。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class LogFilter implements Filter {\n    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)\n            throws IOException, ServletException {\n        System.out.println(\"请求进来：先记日志\");\n        chain.doFilter(req, resp);        \u002F\u002F 放行给下一个 Filter\n        System.out.println(\"响应回去：补一条访问记录\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意 \u003Ccode>FilterChain\u003C\u002Fcode> 这个参数——框架把\"下家\"包装成了链条对象递给节点。放行前是自己的一段前处理，放行后还能做后处理：请求沿链进、响应沿链出，一个 Filter 就是一节双向管道。编码、鉴权、日志、防 XSS……Tomcat 里的每个请求都要穿过这条链。\u003C\u002Fp>\n\u003Cp>**Spring MVC Interceptor。**同款骨架换了个名字：\u003Ccode>preHandle\u003C\u002Fcode> 返回 true 放行、返回 false 拦停，\u003Ccode>postHandle\u003C\u002Fcode>、\u003Ccode>afterCompletion\u003C\u002Fcode> 负责回程处理。登录校验、接口限流、租户路由，实现类排成一条链。\u003C\u002Fp>\n\u003Cp>**Netty Pipeline——把责任链做成了骨架级架构。**每个 Channel 挂一条 ChannelPipeline，编解码器、心跳处理器、业务处理器都是链上的 ChannelHandler，靠 \u003Ccode>ctx.fireChannelRead(msg)\u003C\u002Fcode> 一节节往下传（想拦截就不调用 fire）。Netty 用一条责任链撑起了整个网络事件处理模型——责任链能玩到多大，看 Netty 就知道。\u003C\u002Fp>\n\u003Cp>**OkHttp Interceptor。**RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor……一次 HTTP 请求的\"重试→补头→查缓存→建连接\"全在链上，还开放出口让你加自定义拦截器（比了个耶：透明压缩、统一加签都是这么实现的）。Retrofit、 Glide 的原理也都有它的影子。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是\"请求要经过一串加工站\"的地方，框架作者的第一反应都是责任链。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>六、分派三部曲合订：策略、观察者、责任链\u003C\u002Fh2>\n\u003Cp>连讲三篇的\"分派\"问题，最后合上账本对比一下：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>策略模式 ⑮\u003C\u002Fth>\n   \u003Cth>观察者模式 ⑯\u003C\u002Fth>\n   \u003Cth>责任链模式 ⑰\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>请求给谁\u003C\u002Ftd>\n   \u003Ctd>调用方**指定一个**\u003C\u002Ftd>\n   \u003Ctd>**广播给全部**\u003C\u002Ftd>\n   \u003Ctd>沿链试探，**通常一个接盘**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>决策权\u003C\u002Ftd>\n   \u003Ctd>调用方\u003C\u002Ftd>\n   \u003Ctd>名单上所有人（都执行）\u003C\u002Ftd>\n   \u003Ctd>每个节点自己\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>处理者数量\u003C\u002Ftd>\n   \u003Ctd>1\u003C\u002Ftd>\n   \u003Ctd>N（全执行）\u003C\u002Ftd>\n   \u003Ctd>0 或 1（纯链）\u002F N（管线式）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>耦合方向\u003C\u002Ftd>\n   \u003Ctd>Context 依赖接口\u003C\u002Ftd>\n   \u003Ctd>Subject 依赖接口\u003C\u002Ftd>\n   \u003Ctd>节点只依赖下家\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型场景\u003C\u002Ftd>\n   \u003Ctd>促销算法、支付渠道\u003C\u002Ftd>\n   \u003Ctd>事件广播\u003C\u002Ftd>\n   \u003Ctd>审批流、过滤器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>面试遇到\"这三个模式有什么区别\"，照着这张表说，再把本文三段代码场景各举一例，基本满分。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>责任链模式一句话总结：\u003Cstrong>一串处理者连成链，请求只递给链头；每个节点要么接盘，要么转交，要么兜底喊一嗓子。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ⑱ 命令模式——责任链传的\"请求\"始终只是个参数，命令模式更进一步：\u003Cstrong>把请求本身变成对象\u003C\u002Fstrong>。编辑器的撤销键、任务队列里的一个个任务、可编排的宏录制……\"把干什么打包成对象\"之后，排队、撤销、日志、重放全是白送的。命令模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113935201573801985","design-pattern-31","设计模式修炼之路 ㉛ 拦截过滤器模式——一个过滤器，只管一件事","一个过滤器一个横切关注点、return 走不出的审计盲区、契约两句话放行就 next、后置代码包住整条链、顺序即范围、与责任链对账",1038,7452,19,"2025-05-27T20:35:21",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113932265082851329","design-pattern-18","设计模式修炼之路 ⑱ 命令模式——把请求封装成对象","把请求封装成对象、增量式撤销、白送的排队与重放、Executor 的铁证",2089,7080,18,"2024-08-09T12:36:13",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113933842262790145","design-pattern-19","设计模式修炼之路 ⑲ 状态模式——状态驱动行为","状态驱动行为、流转规则住进状态家、Thread 状态机、状态 vs 策略终局对决",1320,6715,17,"2024-09-02T16:09:32",{"code":4,"message":5,"data":73},{"prev":74,"next":83},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":81,"publishedAt":82},"2113932151245246465","design-pattern-16","设计模式修炼之路 ⑯ 观察者模式——事件订阅与广播","一对多依赖、订阅退订自由、生产环境三块拼图、Spring 事件机制、观察者 vs 发布订阅",3150,6331,16,"2024-07-07T14:57:29",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},{"code":4,"message":5,"data":85},[86,92,97,103],{"id":87,"name":88,"slug":89,"description":90,"sortOrder":4,"articleCount":91},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":93,"slug":94,"description":95,"sortOrder":4,"articleCount":96},"设计模式","design-pattern","设计模式系列","34",{"id":98,"name":99,"slug":100,"description":101,"sortOrder":33,"articleCount":102},"1946000000000000002","随笔","essay","生活与思考","0",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":108,"articleCount":109},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":111,"toc":112},"\u003Cblockquote>\n \u003Cp>行为型第四站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾留了个场景：报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。\u003C\u002Fp>\n\u003Cp>这一篇就把这套\"击鼓传花\"讲透——责任链模式，审批流的经典实现，也是各大框架的骨架模式。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、责任链模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. （让多个对象都有机会处理请求，避免请求的发送者和接收者耦合。把接收对象串成一条链，让请求沿着链传递，直到有对象处理它为止。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把一串处理者连成链，请求进来只递给第一个；每个处理者自己决定——在我手里就处理，不在我手里就传给下家。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Handler（抽象处理者）\u003C\u002Ftd>\n   \u003Ctd>定义处理接口 + 持有下家的引用\u003C\u002Ftd>\n   \u003Ctd>Approver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteHandler（具体处理者）\u003C\u002Ftd>\n   \u003Ctd>自己的权限范围内处理，范围外转交下家\u003C\u002Ftd>\n   \u003Ctd>组长\u002F经理\u002F总监\u002F财务总监\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Client（客户端）\u003C\u002Ftd>\n   \u003Ctd>组链 + 把请求交给链头\u003C\u002Ftd>\n   \u003Ctd>ChainTest\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>类比就是报销审批本身：你（Client）只把单子递给直属组长（链头），组长翻一下额度——300 块，自己就签了；80 万？签不动，转给经理。\u003Cstrong>你从不需要知道整条审批线长什么样，你只认识你组长。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>和前两篇串起来看，行为型已经凑齐了\"分派三部曲\"：⑮ 策略解决\"选哪个算法\"，⑯ 观察者解决\"通知哪些下游\"，⑰ 责任链解决\"请求谁接盘\"——前两个的决策权在调用方\u002F名单，这一个的决策权下放到每个节点自己手里。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>发送者只认识链头\u003C\u002Fstrong>。客户端不知道有几个处理者、怎么路由，耦合度降到最低。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>节点自治\u003C\u002Fstrong>。每个处理者只关心\"我的额度是多少、批还是转\"，节点之间互不认识——除了自己的下家。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态组链\u003C\u002Fstrong>。链条是运行时拼的：加一级审批、临时抽掉一个环节、换个顺序，都是改一行组装代码的事，节点类不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>符合开闭原则\u003C\u002Fstrong>。新加一个 VP 审批层 = 写一个类 + 组链时插进去。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>请求可能没人处理\u003C\u002Fstrong>。链走到头还是没人接，请求就\"落空\"了——本例 200 万报销单就走到了链尾。必须设计好兜底（驳回、默认处理、或者日志告警），否则请求无声消失，是生产事故级 bug。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>调试费劲\u003C\u002Fstrong>。一个请求在链上跳来跳去，出问题时要在每个节点打断点、看日志，不如一个 if-else 直观。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>性能损耗\u003C\u002Fstrong>。权限明明在总监手里，请求还是要从组长、经理白跑两站。链越长、命中越靠后，浪费越多。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>顺序敏感\u003C\u002Fstrong>。组链的人把总监排在组长前面，整条链的逻辑就乱了。链条的正确性靠组装代码保证，编译器帮不了你。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>同一个请求，有多个候选处理者，该谁处理要由\"请求内容 + 处理者能力\"在运行时决定，且发送者不该知道路由规则。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>层级审批\u003C\u002Fstrong>：报销、请假、合同会签——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>规则拦截\u003C\u002Fstrong>：风控系统里\"黑名单→限频→额度\"层层筛查，谁先拦住谁处理\u003C\u002Fli>\n \u003Cli>\u003Cstrong>请求预处理管线\u003C\u002Fstrong>：编码、鉴权、日志、防攻击……每个环节处理自己的部分再放行（见第五节 Servlet Filter）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>事件传递\u003C\u002Fstrong>：GUI 里的事件冒泡，子控件不处理就往父控件传\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：处理者唯一且已知——直接调用；处理者之间要全跑一遍——那是观察者（⑯）的广播；需要所有分支并列可选——那是策略（⑮）。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：把路由权下放给节点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：提交人自己路由\u003C\u002Fh3>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadExpenseSystem\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [组长] 批准报销 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [经理] 批准报销 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 100000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [总监] 批准报销 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1000000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [财务总监] 批准报销 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [无人能批] 金额过大，提交董事会讨论\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 公司新设一个 VP 审批层？各级额度调整？\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 这里全得改，而且所有要审批的地方都得复制一遍这套路由规则\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>又是 if-else 巨无霸？看着像 ⑮ 的病，其实病灶不同：⑮ 的分支是\u003Cstrong>并列的算法\u003C\u002Fstrong>，这里的分支是\u003Cstrong>层级路由\u003C\u002Fstrong>——它的问题不是 if-else 本身，而是\"路由规则\"被焊死在了提交人这边。公司调整额度、新设层级，所有提交审批的代码全要改一遍。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：Handler 拿着下家引用，各自为政\u003C\u002Fh3>\n\u003Cp>第一步，抽象处理者——核心就两个字段一个方法：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Approver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String name;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Approver next;          \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 下一个处理者\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Approver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 组链：返回下一个节点，方便链式调用\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Approver \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Approver \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.next \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> next;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> next;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 处理请求：自己处理，或者传给下家\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，每个具体处理者实现同一套决策逻辑——权限内处理，权限外转交：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LeaderApprover\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Approver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LeaderApprover\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [组长\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] 批准报销 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (next \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [组长\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 元超出权限，转交下一级\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            next.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(amount);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [组长\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] 我是链条最后一环，无人能批，驳回\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>ManagerApprover（≤1 万）、DirectorApprover（≤10 万）、CfoApprover（≤100 万）结构完全相同，只是阈值和称谓不同。\u003C\u002Fp>\n\u003Cp>第三步，客户端组链、递交：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ChainTest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 反面教材：提交人自己路由 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        BadExpenseSystem bad \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadExpenseSystem\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">300\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">8000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">50000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">500000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2000000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解：责任链模式 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Approver chain \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LeaderApprover\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"张组长\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ManagerApprover\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"李经理\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">             .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DirectorApprover\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"王总监\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">             .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CfoApprover\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"赵财务总监\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 客户端只认识链条的第一个节点\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">300\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">8000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">50000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">500000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">approve\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2000000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：提交人自己路由 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长] 批准报销 300.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [经理] 批准报销 8000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [总监] 批准报销 50000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [财务总监] 批准报销 500000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [无人能批] 金额过大，提交董事会讨论\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解：责任链模式 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长张组长] 批准报销 300.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长张组长] 8000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [经理李经理] 批准报销 8000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长张组长] 50000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [经理李经理] 50000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [总监王总监] 批准报销 50000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长张组长] 500000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [经理李经理] 500000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [总监王总监] 500000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [财务总监赵财务总监] 批准报销 500000.0 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [组长张组长] 2000000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [经理李经理] 2000000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [总监王总监] 2000000.0 元超出权限，转交下一级\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [财务总监赵财务总监] 我是链条最后一环，无人能批，提交董事会讨论\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：客户端自始至终只调 \u003Ccode>chain.approve(...)\u003C\u002Fcode>，只认识链头。**金额是 300 还是 200 万，客户端根本不关心——路由决策完全下放给了节点。对比反面教材里提交人背着一整套路由规则，这就是责任链的本质收益。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：每个节点的三段式判断——\"我的额度内 \u002F 我有下家 \u002F 我到头了\"。\u003C\u002Fstrong>\u003Ccode>amount &lt;= 1000\u003C\u002Fcode> 是本职，\u003Ccode>next != null\u003C\u002Fcode> 是转交，\u003Ccode>else\u003C\u002Fcode> 是兜底。第三段最容易被新手漏掉：链尾没有兜底，请求就无声落空，正是第二节缺点一说的生产事故源。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：\u003Ccode>setNext\u003C\u002Fcode> 返回下家引用，组链才能写成一行流式调用。\u003C\u002Fstrong>\u003Ccode>chain.setNext(A).setNext(B).setNext(C)\u003C\u002Fcode>——不是炫技，是让\"链条的形状\"在组装代码里一目了然，一眼看出层级顺序。第五节 Netty 的 Pipeline 组装 API 就是这个味道。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">4.4 纯责任链 vs 管线式责任链\u003C\u002Fh3>\n\u003Cp>本文是教科书上的\u003Cstrong>纯责任链\u003C\u002Fstrong>：请求被某一个节点接住就结束了，后面的节点不再参与。\u003C\u002Fp>\n\u003Cp>但框架世界里更常见的是\u003Cstrong>管线式\u003C\u002Fstrong>：每个节点都处理一下（加工、校验、增强），处理完继续放行，请求穿过整条链——Servlet Filter、Spring Interceptor、Netty Pipeline 全是这种。两者的骨架代码几乎一样，区别只在节点是\"拦截\"还是\"放行\"。第五节展开看实物。\u003C\u002Fp>\n\u003Cp>另外你可能注意到，四个具体处理者的\"三段式\"结构高度重复——这正是 ⑭ 模板模式的用武之地：把\"判断权限→处理→转交\"的骨架提到父类做成 final 方法，子类只提供阈值。责任链管\"链怎么传\"，模板管\"节点内部长什么样\"，两模式叠加毫不冲突。\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">五、在框架里找它\u003C\u002Fh2>\n\u003Cp>责任链是当之无愧的\"框架骨架模式\"，你每天写的每个请求都在链上跑。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Servlet Filter——管线式责任链的祖师爷。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LogFilter\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Filter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> doFilter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ServletRequest \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">req\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, ServletResponse \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">resp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, FilterChain \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">chain\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            throws\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> IOException, ServletException {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"请求进来：先记日志\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        chain.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">doFilter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(req, resp);        \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 放行给下一个 Filter\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"响应回去：补一条访问记录\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意 \u003Ccode>FilterChain\u003C\u002Fcode> 这个参数——框架把\"下家\"包装成了链条对象递给节点。放行前是自己的一段前处理，放行后还能做后处理：请求沿链进、响应沿链出，一个 Filter 就是一节双向管道。编码、鉴权、日志、防 XSS……Tomcat 里的每个请求都要穿过这条链。\u003C\u002Fp>\n\u003Cp>**Spring MVC Interceptor。**同款骨架换了个名字：\u003Ccode>preHandle\u003C\u002Fcode> 返回 true 放行、返回 false 拦停，\u003Ccode>postHandle\u003C\u002Fcode>、\u003Ccode>afterCompletion\u003C\u002Fcode> 负责回程处理。登录校验、接口限流、租户路由，实现类排成一条链。\u003C\u002Fp>\n\u003Cp>**Netty Pipeline——把责任链做成了骨架级架构。**每个 Channel 挂一条 ChannelPipeline，编解码器、心跳处理器、业务处理器都是链上的 ChannelHandler，靠 \u003Ccode>ctx.fireChannelRead(msg)\u003C\u002Fcode> 一节节往下传（想拦截就不调用 fire）。Netty 用一条责任链撑起了整个网络事件处理模型——责任链能玩到多大，看 Netty 就知道。\u003C\u002Fp>\n\u003Cp>**OkHttp Interceptor。**RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor……一次 HTTP 请求的\"重试→补头→查缓存→建连接\"全在链上，还开放出口让你加自定义拦截器（比了个耶：透明压缩、统一加签都是这么实现的）。Retrofit、 Glide 的原理也都有它的影子。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是\"请求要经过一串加工站\"的地方，框架作者的第一反应都是责任链。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">六、分派三部曲合订：策略、观察者、责任链\u003C\u002Fh2>\n\u003Cp>连讲三篇的\"分派\"问题，最后合上账本对比一下：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>策略模式 ⑮\u003C\u002Fth>\n   \u003Cth>观察者模式 ⑯\u003C\u002Fth>\n   \u003Cth>责任链模式 ⑰\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>请求给谁\u003C\u002Ftd>\n   \u003Ctd>调用方**指定一个**\u003C\u002Ftd>\n   \u003Ctd>**广播给全部**\u003C\u002Ftd>\n   \u003Ctd>沿链试探，**通常一个接盘**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>决策权\u003C\u002Ftd>\n   \u003Ctd>调用方\u003C\u002Ftd>\n   \u003Ctd>名单上所有人（都执行）\u003C\u002Ftd>\n   \u003Ctd>每个节点自己\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>处理者数量\u003C\u002Ftd>\n   \u003Ctd>1\u003C\u002Ftd>\n   \u003Ctd>N（全执行）\u003C\u002Ftd>\n   \u003Ctd>0 或 1（纯链）\u002F N（管线式）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>耦合方向\u003C\u002Ftd>\n   \u003Ctd>Context 依赖接口\u003C\u002Ftd>\n   \u003Ctd>Subject 依赖接口\u003C\u002Ftd>\n   \u003Ctd>节点只依赖下家\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型场景\u003C\u002Ftd>\n   \u003Ctd>促销算法、支付渠道\u003C\u002Ftd>\n   \u003Ctd>事件广播\u003C\u002Ftd>\n   \u003Ctd>审批流、过滤器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>面试遇到\"这三个模式有什么区别\"，照着这张表说，再把本文三段代码场景各举一例，基本满分。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">写在最后\u003C\u002Fh2>\n\u003Cp>责任链模式一句话总结：\u003Cstrong>一串处理者连成链，请求只递给链头；每个节点要么接盘，要么转交，要么兜底喊一嗓子。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ⑱ 命令模式——责任链传的\"请求\"始终只是个参数，命令模式更进一步：\u003Cstrong>把请求本身变成对象\u003C\u002Fstrong>。编辑器的撤销键、任务队列里的一个个任务、可编排的宏录制……\"把干什么打包成对象\"之后，排队、撤销、日志、重放全是白送的。命令模式，下一篇见。\u003C\u002Fp>",[113,116,119,122,125,129,132,135,138,141,144],{"id":114,"text":115,"level":108},"toc-1","一、责任链模式是什么",{"id":117,"text":118,"level":108},"toc-2","二、优点与缺点",{"id":120,"text":121,"level":108},"toc-3","三、什么时候用",{"id":123,"text":124,"level":108},"toc-4","四、实现方式：把路由权下放给节点",{"id":126,"text":127,"level":128},"toc-5","4.1 反面教材：提交人自己路由",3,{"id":130,"text":131,"level":128},"toc-6","4.2 正解：Handler 拿着下家引用，各自为政",{"id":133,"text":134,"level":128},"toc-7","4.3 三处细节咂摸一下",{"id":136,"text":137,"level":128},"toc-8","4.4 纯责任链 vs 管线式责任链",{"id":139,"text":140,"level":108},"toc-9","五、在框架里找它",{"id":142,"text":143,"level":108},"toc-10","六、分派三部曲合订：策略、观察者、责任链",{"id":145,"text":146,"level":108},"toc-11","写在最后"]