202515 | 设计模式13 | 访问者模式 | 备忘录模式 | 访问者模式
访问者模式(Visitor Pattern)
1. 定义
访问者模式(Visitor Pattern)*是一种*行为型设计模式,它将数据结构与操作解耦**,使得可以在不修改原数据结构的情况下,新增对其的操作。
在该模式下,访问者(Visitor) 负责定义一系列的操作,而数据结构(元素对象)通过接受访问者(accept 方法)来执行对应的操作。
2. 适用场景
✅ 对象结构较为稳定,但需要频繁扩展操作时(如编译器、XML 解析) ✅ 不同类型的元素需要执行不同的操作(如文件系统遍历) ✅ 需要对对象结构中的不同类型对象进行统一处理
3. 角色划分
| 角色 | 职责 |
|---|---|
| Visitor(访问者) | 定义对数据结构元素的操作。 |
| ConcreteVisitor(具体访问者) | 实现具体的访问逻辑,如打印、计算等。 |
| Element(抽象元素) | 定义 accept(Visitor visitor) 方法,允许访问者访问自己。 |
| ConcreteElement(具体元素) | 具体数据结构,提供 accept 方法,调用访问者的相应方法。 |
| ObjectStructure(对象结构) | 维护元素集合,并允许访问者访问所有元素。 |
4. UML 类图
+-------------------+
| Visitor | (抽象访问者)
+-------------------+
| + visit(A) |
| + visit(B) |
+-------------------+
▲
│
+----------------------+
| ConcreteVisitor | (具体访问者)
+----------------------+
| + visit(A) |
| + visit(B) |
+----------------------+
▲
│
+-------------------+
| Element | (抽象元素)
+-------------------+
| + accept() |
+-------------------+
▲
│
+-------------------+ +-------------------+
| ConcreteElementA | | ConcreteElementB | (具体元素)
+-------------------+ +-------------------+
| + accept() | | + accept() |
+-------------------+ +-------------------+
▲
│
+----------------------+
| ObjectStructure | (对象结构)
+----------------------+
| + addElement() |
| + accept(visitor) |
+----------------------+
5. 代码示例:动物医院
场景:
- **动物(Dog、Cat)是具体元素,它们可以被不同的访问者(Vet 兽医、Trainer 训练师)**检查。
- **访问者(Veterinarian、Trainer)**定义了不同的处理逻辑,如检查健康状况或进行训练。
📌 Java 代码
import java.util.ArrayList;
import java.util.List;
// 1. 访问者接口
interface AnimalVisitor {
void visit(Dog dog);
void visit(Cat cat);
}
// 2. 具体访问者:兽医
class Veterinarian implements AnimalVisitor {
@Override
public void visit(Dog dog) {
System.out.println("兽医检查狗:" + dog.getName());
}
@Override
public void visit(Cat cat) {
System.out.println("兽医检查猫:" + cat.getName());
}
}
// 3. 具体访问者:训练师
class Trainer implements AnimalVisitor {
@Override
public void visit(Dog dog) {
System.out.println("训练师训练狗:" + dog.getName());
}
@Override
public void visit(Cat cat) {
System.out.println("训练师训练猫:" + cat.getName());
}
}
// 4. 抽象元素(动物)
interface Animal {
void accept(AnimalVisitor visitor);
}
// 5. 具体元素:狗
class Dog implements Animal {
private String name;
public Dog(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
// 6. 具体元素:猫
class Cat implements Animal {
private String name;
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
// 7. 对象结构(动物医院)
class AnimalHospital {
private List<Animal> animals = new ArrayList<>();
public void addAnimal(Animal animal) {
animals.add(animal);
}
public void accept(AnimalVisitor visitor) {
for (Animal animal : animals) {
animal.accept(visitor);
}
}
}
// 8. 客户端测试
public class VisitorPatternDemo {
public static void main(String[] args) {
// 创建动物医院
AnimalHospital hospital = new AnimalHospital();
hospital.addAnimal(new Dog("Buddy"));
hospital.addAnimal(new Cat("Kitty"));
// 兽医检查
System.out.println("=== 兽医检查 ===");
hospital.accept(new Veterinarian());
// 训练师训练
System.out.println("\n=== 训练师训练 ===");
hospital.accept(new Trainer());
}
}
6. 运行结果
=== 兽医检查 ===
兽医检查狗:Buddy
兽医检查猫:Kitty
=== 训练师训练 ===
训练师训练狗:Buddy
训练师训练猫:Kitty
7. 访问者模式的特点
✅ 优点
- 解耦数据结构和操作 —— 可以在不修改数据结构的情况下,新增新的访问操作。
- 符合“开闭原则” —— 可以自由扩展访问者,而不影响原数据结构。
- 统一操作逻辑 —— 可以统一对不同元素的操作逻辑,使代码更清晰。
❌ 缺点
- 违反“单一职责原则” —— 数据结构和操作逻辑混在一起,增加代码复杂度。
- 修改数据结构时影响较大 —— 如果数据结构(元素)需要修改,所有访问者都可能需要调整。
- 双分派机制增加复杂性 —— 访问者依赖元素,元素又依赖访问者,可能导致系统难以维护。
8. 访问者模式 vs 其他模式
| 对比点 | 访问者模式 | 策略模式 | 迭代器模式 |
|---|---|---|---|
| 目的 | 分离数据结构和操作 | 允许算法独立于上下文变化 | 统一遍历集合 |
| 扩展性 | 易于扩展新操作 | 易于更换算法 | 易于遍历不同数据结构 |
| 修改影响 | 修改数据结构影响大 | 修改策略影响小 | 修改数据结构影响小 |
9. 适用场景
| 场景 | 示例 |
|---|---|
| 编译器 | 语法树遍历,进行代码优化、代码生成等 |
| 数据库管理 | 统计数据库表的数据量,生成 SQL 语句等 |
| 文件系统 | 访问不同类型文件,进行统计、搜索、压缩等 |
10. 总结
✅ 核心思想:将数据结构和操作分离,允许在不修改数据结构的前提下,增加新操作。 ✅ 关键点:使用 accept() 方法让访问者访问具体元素,实现双分派。 ✅ 适用场景:编译器、文件系统、数据分析等。
🚀 一句话总结:访问者模式让你在不修改原数据结构的情况下,灵活地增加新功能! 🎯
备忘录模式(Memento Pattern)
1. 定义
备忘录模式(Memento Pattern)*是一种*行为型设计模式,用于捕获对象的当前状态,并在未来需要时恢复到这个状态**,同时确保对象的封装性不被破坏。
它通常用于撤销(Undo)、恢复(Redo)功能,比如文本编辑器、游戏存档、事务回滚等场景。
2. 适用场景
✅ 需要提供撤销(Undo)/恢复(Redo)功能时(如 IDE、Office 文档) ✅ 需要存储对象的历史状态,且不能暴露对象的内部数据 ✅ 游戏存档、事务回滚、状态快照等
3. 角色划分
| 角色 | 职责 |
|---|---|
| Originator(发起者) | 负责创建备忘录,并可以恢复状态。 |
| Memento(备忘录) | 存储对象的状态(通常是私有的)。 |
| Caretaker(管理者) | 负责存储和管理备忘录,但不能修改它。 |
4. UML 类图
+------------------+
| Originator | (发起者)
+------------------+
| - state |
| + createMemento()|
| + restore(memento)|
+------------------+
▲
│
+------------------+
| Memento | (备忘录)
+------------------+
| - state |
+------------------+
▲
│
+------------------+
| Caretaker | (管理者)
+------------------+
| - mementoList |
| + addMemento() |
| + getMemento() |
+------------------+
5. 代码示例:文本编辑器撤销功能
场景:
Editor代表文本编辑器(发起者)。EditorMemento代表备忘录,存储编辑器的文本状态。History作为历史记录(管理者),存储多个历史版本。
📌 Java 代码
import java.util.Stack;
// 1. 备忘录类(Memento)
class EditorMemento {
private final String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
// 2. 发起者(Originator)
class Editor {
private String content = "";
public void type(String words) {
content += words;
}
public String getContent() {
return content;
}
// 创建备忘录
public EditorMemento save() {
return new EditorMemento(content);
}
// 恢复状态
public void restore(EditorMemento memento) {
content = memento.getContent();
}
}
// 3. 管理者(Caretaker)
class History {
private Stack<EditorMemento> history = new Stack<>();
public void save(EditorMemento memento) {
history.push(memento);
}
public EditorMemento undo() {
if (!history.isEmpty()) {
return history.pop();
}
return null;
}
}
// 4. 客户端测试
public class MementoPatternDemo {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();
// 输入文本
editor.type("Hello, ");
history.save(editor.save()); // 备份1
editor.type("World!");
history.save(editor.save()); // 备份2
System.out.println("当前内容: " + editor.getContent()); // Hello, World!
// 撤销一次
editor.restore(history.undo());
System.out.println("撤销后: " + editor.getContent()); // Hello,
// 再次撤销
editor.restore(history.undo());
System.out.println("再次撤销后: " + editor.getContent()); // (空)
}
}
6. 运行结果
当前内容: Hello, World!
撤销后: Hello,
再次撤销后: (空)
7. 备忘录模式的特点
✅ 优点
- 提供撤销/恢复功能 —— 适用于编辑器、游戏存档等应用场景。
- 不破坏封装性 —— 备忘录存储状态,不暴露
Originator的内部实现。 - 管理多个历史记录 —— 可以存储多个历史状态,实现多级撤销/重做。
❌ 缺点
- 可能消耗大量内存 —— 如果对象状态过大,存储多个备份会导致内存占用高。
- 管理复杂性较高 —— 需要额外的
Caretaker维护状态,并确保不会滥用备忘录。
8. 备忘录模式 vs 其他模式
| 对比点 | 备忘录模式 | 状态模式 | 命令模式 |
|---|---|---|---|
| 目的 | 记录并恢复对象状态 | 让对象在不同状态下行为不同 | 记录和执行请求 |
| 封装性 | 隐藏内部状态 | 直接修改对象状态 | 记录整个操作 |
| 适用场景 | 撤销、恢复、快照存储 | 状态驱动的对象 | 任务队列、事务管理 |
9. 适用场景
| 场景 | 示例 |
|---|---|
| 文本编辑器 | 记录并恢复文本内容 |
| 游戏存档 | 存储玩家当前进度,可恢复 |
| 事务回滚 | 数据库事务管理 |
10. 总结
✅ 核心思想:将对象状态封装成备忘录,并存储在管理者中,以便未来恢复。 ✅ 关键点:保持对象封装性,防止外部修改内部状态。 ✅ 适用场景:文本编辑、游戏存档、事务回滚等。
🚀 一句话总结:备忘录模式让你可以随时存档,随时回溯,不怕改错! 🎯
解释器模式(Interpreter Pattern)
1. 定义
解释器模式(Interpreter Pattern)*是一种*行为型设计模式,用于定义一种语言的语法表示,并提供一个解释器来解析和执行该语言的语法**。
它通常用于编译器、规则引擎、计算器、表达式求值等场景。
2. 适用场景
✅ 当有一套特定的语法规则(如数学表达式、脚本语言)需要解析和执行时 ✅ 当需要将业务规则表达为可计算的表达式(如 SQL 解析、正则表达式) ✅ 适用于“简单的语言”解析,复杂语言通常使用编译器技术(如 ANTLR、Lex、Yacc)
3. 角色划分
| 角色 | 职责 |
|---|---|
| AbstractExpression(抽象表达式) | 定义解释器接口,所有表达式都实现该接口。 |
| TerminalExpression(终结符表达式) | 直接对输入进行处理,如数值、变量等。 |
| NonTerminalExpression(非终结符表达式) | 组合多个表达式,构成更复杂的语法规则。 |
| Context(上下文) | 存储和维护全局信息,如变量值、运算符优先级等。 |
| Client(客户端) | 负责构造语法树,并调用解释器计算结果。 |
4. UML 类图
+------------------------+
| AbstractExpression | (抽象表达式)
+------------------------+
| + interpret(Context) |
+------------------------+
▲
│
+------------------------+ +------------------------+
| TerminalExpression | | NonTerminalExpression |
| (终结符表达式) | | (非终结符表达式) |
+------------------------+ +------------------------+
| + interpret(Context) | | + interpret(Context) |
+------------------------+ +------------------------+
▲
│
+------------------------+
| Context | (上下文)
+------------------------+
| 变量存储、环境配置 |
+------------------------+
5. 代码示例:四则运算解析
场景:
NumberExpression代表数值。AddExpression代表加法运算。SubtractExpression代表减法运算。Context代表上下文(这里简化,不涉及变量存储)。
📌 Java 代码
import java.util.Stack;
// 1. 抽象表达式
interface Expression {
int interpret();
}
// 2. 终结符表达式(数值)
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
// 3. 非终结符表达式(加法)
class AddExpression implements Expression {
private Expression left, right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
// 4. 非终结符表达式(减法)
class SubtractExpression implements Expression {
private Expression left, right;
public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
// 5. 解析器:构建表达式树
class ExpressionParser {
public static Expression parse(String expression) {
Stack<Expression> stack = new Stack<>();
for (String token : expression.split(" ")) {
if (token.equals("+")) {
Expression right = stack.pop();
Expression left = stack.pop();
stack.push(new AddExpression(left, right));
} else if (token.equals("-")) {
Expression right = stack.pop();
Expression left = stack.pop();
stack.push(new SubtractExpression(left, right));
} else {
stack.push(new NumberExpression(Integer.parseInt(token)));
}
}
return stack.pop();
}
}
// 6. 客户端测试
public class InterpreterPatternDemo {
public static void main(String[] args) {
String expression = "5 3 + 8 -"; // 解析表达式 "(5 + 3) - 8"
Expression parsedExpression = ExpressionParser.parse(expression);
System.out.println("计算结果: " + parsedExpression.interpret()); // 0
}
}
6. 运行结果
计算结果: 0
7. 解释器模式的特点
✅ 优点
- 支持扩展新语法 —— 可以轻松添加新的表达式(如乘法、除法)。
- 符合开闭原则 —— 新增表达式不会影响原有代码。
- 代码结构清晰 —— 直接表达语法结构,使代码更易理解。
❌ 缺点
- 性能问题 —— 解析复杂语言时,递归构建和解释语法树会影响效率。
- 适用于简单语法 —— 复杂语法通常需要**编译器技术(如 ANTLR、正则解析器)**来实现。
- 可读性下降 —— 当语法规则多了以后,解析代码可能变得复杂。
8. 解释器模式 vs 其他模式
| 对比点 | 解释器模式 | 策略模式 | 状态模式 |
|---|---|---|---|
| 目的 | 解析和执行语法规则 | 选择不同算法 | 让对象在不同状态下行为不同 |
| 适用场景 | 解析语言、表达式计算 | 运行时动态选择策略 | 依赖状态的行为变化 |
| 复杂性 | 适用于简单规则 | 代码简单 | 适用于有限状态机 |
9. 适用场景
| 场景 | 示例 |
|---|---|
| 数学表达式计算 | 解析 5 + 3 - 2 这样的表达式 |
| 规则引擎 | 解析和执行业务规则,如促销规则 |
| SQL 解析 | 解析 SQL 查询,如 SELECT * FROM users WHERE age > 18 |
| 正则表达式 | 解析并执行 ^[a-z]+@[a-z]+.com$ 这样的正则 |
10. 总结
✅ 核心思想:用类表示语言的语法,并提供解释器执行。 ✅ 关键点:构建语法树,递归解析表达式。 ✅ 适用场景:数学计算、规则解析、DSL 设计等。
🚀 一句话总结:解释器模式适合小型语言解析,但复杂场景建议使用编译器工具! 🎯
更多推荐



所有评论(0)