责任链模式和策略模式、装饰器模式、观察者模式有什么区别?
简化版
责任链模式关注“请求沿多个处理器传递”;策略模式关注“选择一个算法执行”;装饰器模式关注“层层包装对象增强功能”;观察者模式关注“状态变化后通知多个订阅者”。责任链可以短路或继续传递,策略通常只选一个策略,装饰器强调包装同一对象,观察者强调事件通知。
详细版
四者的核心区别可以这样记:
| 模式 | 关注点 | 典型结构 | 常见场景 |
|---|---|---|---|
| 责任链模式 | 多个处理器按顺序处理请求 | Handler 链 | 过滤器、审批流、校验链 |
| 策略模式 | 多个算法中选择一个执行 | Strategy 接口 + 具体策略 | 支付方式、折扣算法 |
| 装饰器模式 | 给对象动态叠加能力 | 包装同一接口对象 | I/O 流、功能增强 |
| 观察者模式 | 一个事件通知多个订阅者 | Subject + Observer | 事件监听、发布订阅 |
责任链和策略容易混淆:策略模式通常只选一个策略来完成算法;责任链可能经过多个处理器,也可能由某个处理器拦截。
责任链和装饰器也有相似点:都可能形成链式调用。但装饰器强调对象能力增强,责任链强调请求在处理器之间传递。
责任链和观察者都能解耦调用方和处理方。区别是观察者是事件广播,多个观察者通常彼此独立;责任链是有顺序的传递,前一个节点会影响后一个节点是否执行。
完整版教学
一、责任链模式:请求在链上走
责任链模式的关键词是“传递”和“顺序”。
请求进入链后,处理器一个接一个地获得处理机会:
Request -> Handler A -> Handler B -> Handler C
每个处理器都可以决定:
- 当前是否处理;
- 是否继续传给下一个;
- 是否直接拦截;
- 是否修改上下文。
比如网关过滤器链中,限流过滤器可以直接拒绝请求,鉴权过滤器可以拦截未登录请求,日志过滤器可以记录请求后继续放行。
二、策略模式:从多个算法中选一个
策略模式的关键词是“选择”。
比如支付方式:
PayStrategy
-> AliPayStrategy
-> WeChatPayStrategy
-> BankCardPayStrategy
一次支付通常只会选择一种策略执行,而不是把所有支付策略都执行一遍。
策略模式关注的是把算法封装起来,让调用方可以根据条件选择不同算法。它不强调链式传递,也不强调前一个策略决定后一个策略是否执行。
三、装饰器模式:围绕同一个对象层层增强
装饰器模式的关键词是“包装”和“增强”。
比如 Java I/O:
InputStream in = new BufferedInputStream(new FileInputStream("a.txt"));
BufferedInputStream 包装了 FileInputStream,增加缓冲能力,但对外仍然表现为 InputStream。
装饰器模式中的每一层通常都实现同一个接口,并持有被装饰对象。它强调在不修改原对象的情况下叠加功能。
责任链也可能像一层层调用,但它不一定包装同一个对象,也不一定对外表现为同一业务对象。它更关心请求在多个处理者之间如何流动。
四、观察者模式:事件发生后通知多个订阅者
观察者模式的关键词是“通知”。
比如订单支付成功后:
支付成功事件 -> 短信观察者
支付成功事件 -> 积分观察者
支付成功事件 -> 营销观察者
观察者之间通常没有严格的链式关系,一个观察者处理失败,理论上不应该随意影响其他观察者,除非业务明确要求。
责任链则不同。责任链有明确顺序,前面的处理器可能拦截请求,导致后面的处理器不执行。
五、从“数量”和“顺序”角度快速区分
可以用两个问题区分:
第一个问题:一次请求会执行几个处理对象?
- 策略模式:通常执行一个;
- 观察者模式:可能通知多个;
- 责任链模式:可能一个,也可能多个;
- 装饰器模式:多个包装层共同增强一个对象。
第二个问题:处理对象之间有没有顺序依赖?
- 责任链:通常有顺序;
- 装饰器:包装层有调用嵌套顺序;
- 观察者:一般不强调顺序;
- 策略:通常不存在多个策略顺序执行。
六、它们可以组合使用
真实项目里模式不是互斥的。
比如风控系统可以这样设计:
- 整体风控流程是一条责任链;
- 每个风控节点内部使用策略模式选择具体算法;
- 日志能力用装饰器增强某个风控服务;
- 风控命中后发布观察者事件通知运营系统。
面试时能说出“可以组合使用”,说明你理解的是设计意图,而不是只背结构图。
七、从执行数量和目的区分四种模式
同一请求:策略从 4 个算法中选 1 个;责任链最多走 4 个候选;装饰器按配置执行 4 层增强;观察者向 4 个订阅者各通知 1 次。
strategy choose one | chain pass/stop | decorator wrap all | observer fan-out
责任链是否正确,不能只看节点都被注册,还要看请求到底经过哪些节点、在哪里停止以及返回阶段如何展开。应把“继续、已处理、拒绝、系统失败”建成可区分的结果,并让日志能够还原一次请求的完整路径。
八、链路顺序、短路与可观测性
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 区分模式必须同时回答谁选择、执行几个、是否有顺序、主要意图是什么。 |
| 适用边界 | 结构可能组合出现,例如责任链节点内部选择策略,链入口外再套日志装饰器,结束后发布事件。 |
| 测试证据 | 对同一请求分别验证首节点命中、中间节点命中和走到链尾,断言实际执行节点集合 |
| 工程代价 | 重点测量链长、各节点耗时、最坏路径延迟和短路率,并确认动态装配不会产生重复节点或排序漂移 |
易错点:区分模式必须同时回答谁选择、执行几个、是否有顺序、主要意图是什么。
九、常见误区与追问
- 误区:看到 List
就一定是责任链。 策略注册表和观察者列表也可能使用 List,容器结构不能替代行为语义。 - 误区:责任链一定会让每个处理器都执行。 纯链可能命中一个节点就结束,不纯链也可能因拒绝、异常或短路提前终止,必须由链契约定义。
- 追问:缓存命中后停止后续处理更像什么? 若请求沿处理器链并由缓存节点短路,可视为责任链;若对象替身控制目标访问,则也有代理意图。
- 追问:责任链如何避免请求到链尾无人处理? 提供明确的默认处理器或链尾失败结果,并对未处理请求记录指标,不能静默成功。
- 追问:责任链上线后最少要记录哪些信息? 至少记录链版本、节点顺序、逐节点耗时与决策、短路位置、异常和最终处理结果。
十、加强记忆
责任链看请求传递,策略看算法选择,装饰器看对象增强,观察者看事件通知。责任链最重要的是顺序、短路和链路节点;策略通常选一个;装饰器层层包装同一接口;观察者把事件广播给订阅者。区分它们时抓住“是否链式传递、是否只选一个、是否包装对象、是否事件通知”。