news 2026/10/13 16:19:58

行为型模式收官:迭代器、备忘录、职责链、解释器、访问者实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
行为型模式收官:迭代器、备忘录、职责链、解释器、访问者实战解析

1. 行为型模式的最后一课:先把11种模式的版图铺开

写行为型模式(三)之前,我自己先对着GoF的23种设计模式总数盘了一遍。创建型5种、结构型7种,剩下的11种全部属于行为型,是三大类里数量最多、也是最容易让初学者崩溃的一块。很多读者学到行为型时会明显感觉节奏不对:创建型和结构型更多在讲“类怎么搭、对象怎么组装”,一眼能看出结构;行为型却到处都是“对象之间怎么协作、怎么分配职责、怎么交流”,没有一幅类图压阵,立刻就觉得飘了。

行为型模式的核心,说穿了只有一句话:把对象之间的行为协作从硬编码中抽离出来,通过一系列成熟的协作模型,让扩展新行为、调整调用链路、控制对象状态变得更加可控。这11种模式的分布,我在教学和面试辅导中习惯把它们分成“两条半轴线”:一条偏向“算法的封装与替换”,包括模板方法、策略、命令;另一条偏向“状态和变化的感知”,包括观察者、状态、中介者;剩下的迭代器、备忘录、职责链、解释器、访问者这5种,更像是一群“既有流程思维又有结构思维的混合型选手”。

如果你已经看完了这个系列的前两篇,应该对模板方法、策略、命令、观察者、状态、中介者有了整体印象。那这一篇要补上的,就是最后5种:迭代器、备忘录、职责链、解释器、访问者。它们有个共同特征:看起来都不算复杂,但考试、面试、写业务代码时踩坑的概率非常高。因为前6种在框架和工具里你能高频见到,后5种更像“你平时用它但根本没意识到这就是模式”。

这篇文章我会按“每一种模式解决什么痛点、结构怎么摆、代码怎么落地、常见的坑在哪里”这种顺序来讲,而不是只给一段UML图然后甩标准代码。对于准备软考、期末考和面试的人来说,后5种尤其需要理解清楚“为什么要这样设计”,因为选择题和八股问答考的就是你分辨模式的能力。对于想真正写代码的人,我也准备了能直接跑的Java示例和实践中我会调整的细节。

2. 迭代器与备忘录:遍历与回滚的两种“底层思维”

2.1 迭代器模式:把遍历的主动权交给调用方

迭代器模式可能是大家最早接触的GoF模式之一,只是很少人把它当成模式来学。你用Java写一个for (String s : list),底层就是Iterator。你用C++遍历std::vector<int>,底层的begin()和end()返回的也是迭代器。模式定义说得比较文绉绉:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

为什么要绕这么一圈?我打一个生活中的比方。自助餐厅的取餐台是聚合对象,你端着盘子从凉菜区走到热菜区再走到甜品区,你不需要知道厨房内部怎么摆放食材、后厨有几个仓库,你只需要有人告诉你“下一个窗口在哪”。迭代器承担的就是“取餐引导员”的角色,它掌握了当前走到哪了、下一步去哪,而取餐台本身不必把所有档口都摊在你面前。

在实际的Java代码里,迭代器模式主要体现在Iterable和Iterator这两个接口。Iterable约定“这个集合可以被遍历”,Iterator约定“我可以按顺序给你下一个元素”。我们自己动手写一个极简迭代器,能很清楚地看到模式的骨架。比如我有一个“消息队列”类,内部用一个ArrayList存消息,但我不想让外部直接拿到这个List去随便增删,于是让内部类实现迭代器:

public class MessageQueue implements Iterable<Message> { private final List<Message> messages = new ArrayList<>(); public void add(Message msg) { messages.add(msg); } @Override public Iterator<Message> iterator() { return new Iterator<>() { private int index = 0; @Override public boolean hasNext() { return index < messages.size(); } @Override public Message next() { if (!hasNext()) { throw new NoSuchElementException("没有更多消息了"); } return messages.get(index++); } }; } }

这种写法的好处不只是“隐藏内部结构”。调用方拿到的是迭代器接口,那么内部无论是ArrayList、LinkedList还是一棵二叉树,都不影响外部遍历方式。更进一步,你可以同时开多个迭代器,各自维护自己的游标位置,互不干扰;如果你直接对外暴露List,别人就能在遍历中途remove,很容易把程序搞出并发修改异常。

从面试角度,我比较建议大家记住一个点:迭代器模式的本质是“将遍历行为从聚合对象中分离”。问到你“为什么要用迭代器而不是直接for循环访问数组”,标准回答是:对数组可以用下标,但对树、图、自定义聚合结构,下标遍历不通用;迭代器把“怎么遍历”封装在迭代器内部,聚合对象和调用方都解耦了。

2.2 备忘录模式:给对象开一扇“后悔药”窗口

备忘录模式是我见过最直白的模式之一。定义听起来也很朴素:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后能将对象恢复到原先保存的状态。

你可以把它理解成游戏存档。你打一个RPG游戏,打Boss之前存个档,打输了读档再战。存档文件里记录了你当前的血量、蓝量、位置、经验值,这些就是“备忘录”。游戏主体不直接暴露自己内部的所有字段,而是提供一个方法生成存档,另一个方法从存档恢复。外部只管保存和找回存档文件,不需要知道存档里到底有哪些字段。

从结构上看,备忘录模式有三个角色:发起人Originator(需要被存档的对象)、备忘录Memento(保存状态的快照对象)、管理者Caretaker(负责保管备忘录,不修改备忘录内容)。关键约束在于:管理者不能碰备忘录的内部数据,否则就破坏了封装性。Java里实现这个约束最常见的方式是把备忘录类作为发起人的内部类,甚至用私有内部类,让外部根本拿不到具体的字段结构,只能把它当成一个不透明对象保存。

我写一个文本编辑器的撤销快照示例:

public class TextEditor { private StringBuilder content = new StringBuilder(); public void write(String text) { content.append(text); } public String getContent() { return content.toString(); } // 生成快照 public Snapshot save() { return new Snapshot(content.toString()); } // 从快照恢复 public void restore(Snapshot snapshot) { this.content = new StringBuilder(snapshot.getSavedContent()); } // 备忘录作为内部类,外部只能持有引用,不能修改内容 public static class Snapshot { private final String savedContent; private Snapshot(String content) { this.savedContent = content; } private String getSavedContent() { return savedContent; } } }

外部使用时,用一个History类去保存一堆Snapshot,要做撤销就调restore。这里有个容易被忽略的点:Snapshot里的字段必须是不可变的。如果快照保存的是一个对象引用,而发起人后续又改了那个对象的内部字段,快照就被污染了。所以保存字符串时我用的是String而不是StringBuilder,正是为了隔离风险。

真正在业务落地时,备忘录模式最常见的场景是:编辑器撤销、数据库事务回滚、分布式系统中的补偿事务。你会发现这些场景都有一个共同特征——系统需要“回到过去某个安全状态”。不过我要泼一盆冷水:如果你只是在做普通Web业务,尽量不要为一个实体类频繁保存全量快照,内存开销会失控。真要做,也应该考虑增量备份、压缩或者只备份关键字段,而不是无脑深拷贝整个对象图。

2.3 两个模式放在一起看:迭代器负责“向前”,备忘录负责“向后”

把迭代器和备忘录放在同一篇文章里讲,很多人会觉得它们八竿子打不着。但如果你从“行为控制”的角度看,它们其实是一对方向相反的机制:迭代器帮你稳定、安全地向前遍历聚合对象;备忘录帮你可靠地回退到历史状态。一个控制系统未来的访问顺序,一个控制系统过去的状态。

这种对比不是纯理论。我实际做过一个数据迁移工具:要从一个老系统读取上百万条记录,再根据失败情况分批回滚。读取的时候,我用迭代器模式把不同数据源(MySQL、Oracle接口、甚至远程Excel)封装成统一的遍历入口;回滚的时候,我用备忘录模式保存每个批次的导入快照,批次失败就能精确撤销,不会影响其他批次。

很多软考和面试题喜欢把这两个模式作为“低难度送分题”,但同时也会把它们的细节挖得很深。比如迭代器常考“快速失败机制(fail-fast)”,也就是集合结构在迭代过程中被修改时,迭代器会立刻抛出ConcurrentModificationException;备忘录则常考“快照的封装性怎么保证”“多级撤销怎么设计”。这些细节就是你和其他候选人拉开差距的地方,光背UML图是答不出来的。

3. 职责链与解释器:层层上报与文法翻译的精髓

3.1 职责链模式:谁来处理,由链上的节点说了算

职责链模式是我在中间件代码里见到最多的行为型模式之一。它的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

生活里的例子最好懂:你在一家公司提交一笔报销审批单。小组长先看,金额超过1000就往上递交;部门经理再看,超过5000再往上递交;总监再看……每个审批人只处理自己职权范围内的请求,处理不了就往后传。这种审批流天然就是一条职责链。

在代码结构上,职责链模式一般会让所有处理器实现同一个抽象接口,接口里有一个“设置下一级处理器”的方法,还有一个“处理请求”的方法。每个处理器处理不了时,就把请求交给next。我用一个电商订单风控的例子来写,场景比报销更贴近开发者的日常:

public abstract class RiskHandler { protected RiskHandler next; public void setNext(RiskHandler next) { this.next = next; } public abstract boolean handle(Order order); protected boolean nextOrPass(Order order) { if (next == null) { return true; // 整条链都通过了 } return next.handle(order); } } public class BlacklistHandler extends RiskHandler { @Override public boolean handle(Order order) { if (checkBlacklist(order.getUserId())) { System.out.println("命中黑名单,拒绝下单"); return false; } return nextOrPass(order); } } public class FrequencyHandler extends RiskHandler { @Override public boolean handle(Order order) { if (overFrequencyLimit(order.getUserId())) { System.out.println("下单频率过高,触发限制"); return false; } return nextOrPass(order); } } public class AmountHandler extends RiskHandler { @Override public boolean handle(Order order) { if (order.getAmount() > 50000) { System.out.println("大额订单,转人工审核"); return true; } return nextOrPass(order); } }

你会发现职责链模式的最大价值在于:请求的发送者完全不知道谁会处理自己的请求,新增一个风控规则也只需要新增一个Handler并追加到链上,对已有代码零修改,高度符合开闭原则。像Spring MVC的拦截器、Servlet的Filter、Java日志框架里的Logger链,本质上都是职责链的工程化变体。

但这里我要重点提醒一个坑:职责链不是非要“链到头”。设计时要想清楚三个问题:第一,请求走到链尾都没人处理怎么办,是默认通过还是默认拒绝?第二,链上某个处理器抛异常,是中断整条链还是跳过?第三,处理器之间的顺序是否敏感,比如黑名单检查和频率检查顺序互换,结果是否还一样?我在实际项目里就见过一个很典型的事故:因为某个新人在过滤器链上把“身份鉴权过滤器”排在了“参数校验过滤器”之后,导致非法请求先打到了数据库,参数校验通过后才发现token过期,浪费了资源不说,还污染了日志排查链路。

3.2 解释器模式:小语言的优雅实现,也是类爆炸的开始

解释器模式是行为型模式里最容易被高估、也最容易被误解的一个。定义是:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

听起来非常抽象,但你几乎每天在用它的工程化产物:正则表达式引擎、SQL解析器、Spring的SpEL表达式、编译器的语法分析器,都是解释器思想。它解决的问题是:如果一段“规则”以字符串形式输入,而这段规则的组合方式是无限多的,你不可能为每一种组合写一个if-else,这时候就需要定义一套“文法”,让程序能读懂这句话并执行。

为了让你彻底理解,我写一个经典到不能再经典的“加减法表达式计算器”。假设表达式是"3 + 5 - 2",用解释器模式解析它,至少要定义两个角色:终结符表达式(数字)和非终结符表达式(加法、减法)。这里的“终结符”你可以理解成表达式中不能再拆分的原子单位,数字3就是一个终结符;“非终结符”则由多个子表达式组合而成,比如3 + 5整体是一个加法表达式。

public interface Expression { int interpret(); } public class NumberExpression implements Expression { private final int number; public NumberExpression(int number) { this.number = number; } @Override public int interpret() { return number; } } public class AddExpression implements Expression { private final Expression left; private final Expression right; public AddExpression(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() + right.interpret(); } }

定义好之后,客户端要做的就是用这些表达式对象拼成一棵“语法树”,然后调用根节点的interpret()。你会不会觉得这套东西又麻烦又绕?确实。如果只是做计算器,直接写个栈扫描字符串比这简单得多。解释器模式真正的适用边界是:当规则数量多且频繁变化,并且规则可以递归组合时,它的优势才体现出来。

我很负责任地告诉你,绝大多数业务项目根本不需要自己实现解释器。因为实现一个文法需要考虑优先级、括号、词法分析、语法错误处理,代码量会迅速膨胀,类数量成倍增加。真正到了需要解析复杂语言的程度,正确做法往往是引入成熟的解析器库,比如Java里的ANTLR,或者干脆用现成的表达式引擎,如Aviator、SpEL。解释器模式更像你考试要懂、面试要能讲,但实际工程中必须克制使用的模式。过度设计的第一标志,就是有人拿解释器模式去写一个只需要三五个分支判断的规则系统。

3.3 常见误区:责任链不是状态机,解释器不是正则的替代品

把这两类模式放在一起后,我发现很多新手会把它们跟相邻概念搞混。职责链常被误当成状态机:其实职责链的核心是“请求在节点间传递,每个节点决定自己要不要处理”;状态机的核心是“对象内部状态变化驱动行为切换”,前者关注请求分配,后者关注状态转移。举个对比,购物订单“待支付→已支付→已发货→已完成”是状态机;而“先风控校验→再库存校验→最后支付校验”是职责链,两者不该混用。

解释器模式则常被误解成“正则表达式的一种Java写法”,甚至有人试图用它替代正则去解析一切文本。实际上解释器适合的是“结构化、可递归组合的规则”,正则适合的是“按模式匹配的字符串”,两者的抽象层级完全不同。比如你要解析"IF age > 18 THEN allow"这种业务规则,正则能匹配出关键字,但没法优雅地构建规则语义;解释器却可以从文法层面直接定义出条件表达式和动作表达式。

在软考和面试中,这两种模式最容易出现在“给出场景,让你选设计模式”的题目里。只要看到“多个处理器都可以处理请求,按顺序尝试,直到有人处理”,基本就是职责链;只要看到“自定义规则语言、表达式解析、语法树”,基本就是解释器。这种判断速度需要练,而且要在理解了模式本质之后再练,单纯背场景关键词很容易翻车。

4. 访问者模式:让数据结构与操作互相松绑

4.1 双重分派的核心机制

访问者模式是GoF 23种设计模式里公认最难讲清楚、也最容易被忽略的一个。它的意图很特别:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

翻译成人话:我们有一堆对象,它们稳定不变;但我们想给这堆对象不断添加新操作。访问者模式就是把这些新操作集中放到一个“访问者”类里,让对象结构答应接待这个访问者。做到这一点的关键在于“双重分派”,这也是访问者模式唯一真正的技术门槛。

为了理解双重分派,你可以先想普通的方法调用。Java里调用obj.method(arg),实际执行哪个方法,取决于两点:obj的运行时类型,以及arg的编译时类型。大多数场景只有一重分派,也就是对象的动态类型决定行为。访问者模式则要正向调用和反向回调配合:客户端调用元素的accept(visitor)方法,元素再反过来调用visitor.visit(this),this的动态类型在这一步被精确传递,最终触达的是“访问者中针对该具体元素类型的重载方法”。这两个调用叠加在一起,就是双重分派。

我还是用一个具体例子来兜底。假设你有一个文件系统,里面有三种文件:文本文件、图片文件、视频文件。这些文件的类相对稳定,不会频繁改动。但针对它们的操作很多,比如统计大小、扫描敏感词、生成缩略图、转码。如果每个操作都往文件类里加方法,文件类会越来越臃肿,而且每次新操作都要改已有的类,违反开闭原则。

访问者模式的解法是:让文件类只提供一个accept(FileVisitor visitor)方法,把所有对外操作外包给访问者实现。文本文件收到访问者后,调用visitor.visit(TextFile this);图片文件调用visitor.visit(ImageFile this);视频文件调用visitor.visit(VideoFile this)。每个访问者可以自己实现三套不同的处理逻辑,互不干扰。

4.2 Java示例:为多种文件类型设计统计报表

下面我用一段精简代码展示访问者模式的标准形态。首先是元素接口和具体元素:

public interface FileElement { void accept(FileVisitor visitor); } public class TextFile implements FileElement { private String name; private long size; public TextFile(String name, long size) { this.name = name; this.size = size; } public String getName() { return name; } public long getSize() { return size; } @Override public void accept(FileVisitor visitor) { visitor.visit(this); } } public class ImageFile implements FileElement { private String name; private long size; // 构造方法、getter... @Override public void accept(FileVisitor visitor) { visitor.visit(this); } }

然后是访问者接口和具体访问者。注意访问者接口必须为每一种具体元素准备一个visit重载方法:

public interface FileVisitor { void visit(TextFile file); void visit(ImageFile file); } public class SizeReportVisitor implements FileVisitor { private long totalSize = 0; @Override public void visit(TextFile file) { totalSize += file.getSize(); } @Override public void visit(ImageFile file) { totalSize += file.getSize(); } public long getTotalSize() { return totalSize; } }

客户端就可以这样使用:

List<FileElement> files = Arrays.asList( new TextFile("readme.txt", 100), new ImageFile("logo.png", 2048) ); SizeReportVisitor visitor = new SizeReportVisitor(); for (FileElement file : files) { file.accept(visitor); } System.out.println("总大小: " + visitor.getTotalSize());

这个例子做完,你会直观感受到访问者模式的强大和麻烦:新增一种操作时,只需要新建一个访问者实现类,文件类一行不用改;但新增一种文件类型时,所有访问者接口和实现类都要跟着改。说白了,访问者模式把“易变”从数据结构挪到了操作上,适合操作频繁增加、数据结构相对固定的系统,比如编译器中的AST遍历、报表系统、文档导出工具。

4.3 什么时候该放弃访问者

访问者模式是典型的“高收益高风险”模式,我自己在代码评审里看到它时,第一反应通常是警惕。因为它带来的抽象成本很高,如果我看到有人只为了一两个操作就引入访问者,我会直接建议改成简单的instanceof判断或者策略模式。

放弃访问者的信号也很清晰:第一,你的对象结构本身经常变化,比如每半年加一种新文件类型,这时访问者模式会让所有访问者接口跟着改,比直接改元素类还痛苦;第二,对象类型数量很少,两三种而已,instanceof分支完全能搞定,没必要为双重分派引入额外层级;第三,你想要的操作本质上只是“对同一组数据做不同算法”,这更适合策略模式而不是访问者模式。策略模式是针对同一个对象或同一组对象选择算法,访问者模式是针对不同类型对象执行不同逻辑,两者的出发点完全不同。

还有一个实际业务里容易被忽视的问题:访问者模式会让代码的调用链路变得隐晦。你点开TextFile.accept(),发现它只是调了visitor.visit(this),真正的业务逻辑散落在各个访问者类中。对不熟悉这套模式的同事来说,阅读成本很高。如果团队里有人不太理解双重分派,我宁愿不用访问者,也不愿给团队埋下认知负担。

5. 后五种模式的横向对比与软考/面试记忆法

5.1 一张表对齐“痛点、场景、记忆抓手”

把迭代器、备忘录、职责链、解释器、访问者这5种模式放一起,我用下面这张表把它们各自的痛点、典型场景和一句话记忆抓手对齐,备考和面试前扫一眼很有用。

模式解决的核心痛点典型场景一句话记忆抓手
迭代器遍历方式与聚合对象内部结构耦合集合遍历、数据库游标、树结构前序/中序访问不暴露内部结构也能按顺序访问
备忘录对象状态无法在不破封装的前提下保存恢复编辑器撤销、游戏存档、事务回滚存档、读档、后悔药
职责链请求发送者与处理者之间耦合严重审批流、日志过滤器、中间件拦截器层层上报,谁能处理谁处理
解释器自定义规则语言难以用if-else穷举表达式计算、SQL解析、规则引擎定义文法,构建语法树,解释执行
访问者数据结构稳定,但操作频繁扩展编译器AST、报表统计、文件批量处理操作上门拜访,双分派定类型

这张表的妙处在于,它把5种模式全部压缩成了“痛点”和“记忆抓手”。我在软考复习时也经常和学生说:不要死记UML图里的类名,先记住“每个模式要解决的那个不舒服”,再反推结构,正确率会高很多。比如你看到“多个对象都可以处理同一个请求”,直接锁定职责链;看到“需要保存对象历史状态以便撤销”,直接锁定备忘录;看到“遍历某种集合时不想暴露内部实现”,直接锁定迭代器。

5.2 容易混淆的三对模式:别栽在选择题里

软考和面试里,比“不认识模式”更可惜的,是把几个相近模式搞混。我总结了三对最容易错的组合:

第一对:职责链和状态模式。状态模式的触发条件是“内部状态变化”,对象自己改变自己的行为;职责链的触发条件是“外部请求进入”,链上节点各自判断要不要拦截。比如订单状态流转属于前者,而请求经过Token校验、参数校验、权限校验属于后者。

第二对:策略模式和访问者模式。初学者经常搞混,因为它们都涉及“对同一种输入做多种处理”。但策略模式是“同一对象、同类数据、互换算法”,比如排序算法换成快排或归并;访问者是“不同对象、不同数据、执行各自不同的逻辑”,比如文本文件计字数、图片文件算尺寸。记忆方法是:策略是“换套路”,访问者是“分人接待”。

第三对:解释器模式和命令模式。命令模式是把一个请求封装成对象,强调“请求的延迟执行、撤销、记录”;解释器模式强调“按文法解析一条语言句子并计算”。你写一个SaveCommand用于编辑器保存,那是命令;你写一个AddExpression用于计算1+2,那是解释器。

这三对如果再配上一道典型题:如果题目中出现“一个系统支持多种文件格式导出,并且希望以后不断添加新的导出格式,而不修改原有文件类”,答案通常是访问者;如果出现“系统要支持多种不同的压缩算法,可以动态切换”,答案通常是策略。这种题型每年软考都会出,练熟之后基本是送分。

5.3 针对软考选择题的速记口诀和判断技巧

网络上有不少23种设计模式的记忆口诀,但很多口诀只把模式名串起来,没有告诉你怎么对应场景。我自己使用下来,觉得更可靠的是把这一篇的5种模式串成一句话:“遍录职解访”——遍历靠迭代器,撤销录档靠备忘录,请求流转靠职责链,规则解析靠解释器,结构操作靠访问者。每次做题前,先念一遍这5个字,然后对照题目里的“关键词场景”去匹配。

判断题感也非常重要。我看到很多学生在软考上午题里能做对,真到了下午案例分析题反而卡住。原因是选择题只需要你“认出模式”,案例题需要你“说出为什么选它”。我的建议是:在识别出模式后,强迫自己多说三句话——它能解决什么耦合问题?它涉及哪几个角色?它违背了哪条设计原则会怎样?比如你判断出该用迭代器,就说它隔离了聚合对象与遍历逻辑,如果不用它,调用方就得自己拆解内部List结构。这样的答题深度,远比堆模式名词更容易拿分。

另外一个很实用的技巧是:拿到场景题后,先找“不变量”和“变化量”。不变量适合放进被稳定的类里,变化量适合放进被扩展的类或接口里。访问者模式的不变量是文件元素类型,变化量是操作,所以操作放进访问者;策略模式的不变量是业务目标,变化量是算法,所以算法放进策略接口。这个判断逻辑比背场景关键词更本质,对真实项目设计同样适用。

6. 写在最后:第三篇之后,行为型模式怎么收官

如果你能跟着文章把迭代器、备忘录、职责链、解释器、访问者这5种模式挨个过完,行为型模式这条线基本就闭环了。但我个人还是想说一句:别满足于“认识”这5种模式,要努力做到“在代码里认出来”。

我自己当年学访问者模式时,总觉得它是理论玩具,直到后来看编译器源码里的AST遍历部分,看到accept和visit成对出现,才真正把它们对上号。职责链也是,我第一次用Spring Security时,看到过滤链上一堆Filter,才意识到这不就是职责链在框架里的量产实践吗。设计模式的学习路径永远是:先抽象理解,再代码实现,最后在成熟框架里反向印证。

如果你正在赶软考或者期末,我的建议是把这一篇和本系列的前两篇合并成一张模式总表,按“创建型5种、结构型7种、行为型11种”的维度复习。对于行为型的11种,重点记住策略、观察者、状态、命令、职责链这5个高频率考点,其余模式理解到“能识别、能画出结构、能说出使用场景”的程度就够了。很多大作业和面试项目,用不上把23种模式都堆进去,能用对三五个关键模式,反而比强行使用十几个模式要高得多。

回到博客本身,行为型模式(三)里的5种模式,其实是整套设计模式里“思维含量”偏高的一组。迭代器和备忘录像一对时间方向相反的兄弟,职责链和解释器一个管请求路由、一个管规则解析,访问者则用双重分派把“开闭原则”玩到极致。把这些逻辑揉进自己的知识体系,以后再看到复杂代码,你就不会只看到一堆类在互相调来调去,而是会自然地问一句:这背后是不是藏了某个模式?这种看代码的“透视感”,才是设计模式真正回馈给你的东西。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 16:17:01

TensorFlow数据加载实战:从内存到磁盘的tf.data管线优化指南

1. 数据加载为什么成了TensorFlow实战的第一道坎很多人刚接触TensorFlow的时候&#xff0c;注意力全在模型结构上——卷积层怎么搭、注意力机制怎么写、损失函数怎么选。结果真正跑起项目来&#xff0c;第一个卡住的地方往往不是模型&#xff0c;而是数据进不去。我见过太多这样…

作者头像 李华
网站建设 2026/10/13 16:13:46

上下文工程实战:从线上事故到RAG与Agent的上下文管理

1. 从一个被忽视的线上事故说起去年冬天&#xff0c;我参与维护的一个智能问答系统出了一次不大不小的状况。用户反馈说&#xff0c;同一个问题&#xff0c;早上问和下午问&#xff0c;得到的答案质量完全不一样。早上回答得条理清晰、引用准确&#xff0c;下午就开始胡言乱语&…

作者头像 李华
网站建设 2026/10/13 16:08:50

ASP环境JSON解析库JSON_2.0.4完整版实战指南

简介&#xff1a;本资源是面向ASP与Visual Basic开发者的JSON数据处理工具包&#xff0c;专为Web后端动态页面开发及VB桌面应用中JSON序列化/反序列化需求设计&#xff0c;解决传统ASP环境缺乏原生JSON支持、VB项目手动解析效率低等实际问题。压缩包共4个文件&#xff0c;含2个…

作者头像 李华
网站建设 2026/10/13 16:08:31

CATIA V5R13汽车线束设计全流程:从原理图到展平出图的避坑指南

简介&#xff1a;CATIA V5R13汽车线束讲解是一份面向CATIA电气初学者及汽车线束设计人员的PPT课件&#xff0c;系统介绍电气安装&#xff08;Electrical Harness Installation&#xff09;工作台中的线束建模流程&#xff0c;包括几何线束组与线束段的定义、分段、联结管理、支…

作者头像 李华