访问者模式(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. 访问者模式的特点

✅ 优点

  1. 解耦数据结构和操作 —— 可以在不修改数据结构的情况下,新增新的访问操作。
  2. 符合“开闭原则” —— 可以自由扩展访问者,而不影响原数据结构。
  3. 统一操作逻辑 —— 可以统一对不同元素的操作逻辑,使代码更清晰。

❌ 缺点

  1. 违反“单一职责原则” —— 数据结构和操作逻辑混在一起,增加代码复杂度。
  2. 修改数据结构时影响较大 —— 如果数据结构(元素)需要修改,所有访问者都可能需要调整。
  3. 双分派机制增加复杂性 —— 访问者依赖元素,元素又依赖访问者,可能导致系统难以维护。

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. 备忘录模式的特点

✅ 优点

  1. 提供撤销/恢复功能 —— 适用于编辑器、游戏存档等应用场景。
  2. 不破坏封装性 —— 备忘录存储状态,不暴露 Originator 的内部实现。
  3. 管理多个历史记录 —— 可以存储多个历史状态,实现多级撤销/重做。

❌ 缺点

  1. 可能消耗大量内存 —— 如果对象状态过大,存储多个备份会导致内存占用高。
  2. 管理复杂性较高 —— 需要额外的 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. 解释器模式的特点

✅ 优点

  1. 支持扩展新语法 —— 可以轻松添加新的表达式(如乘法、除法)。
  2. 符合开闭原则 —— 新增表达式不会影响原有代码。
  3. 代码结构清晰 —— 直接表达语法结构,使代码更易理解。

❌ 缺点

  1. 性能问题 —— 解析复杂语言时,递归构建和解释语法树会影响效率。
  2. 适用于简单语法 —— 复杂语法通常需要**编译器技术(如 ANTLR、正则解析器)**来实现。
  3. 可读性下降 —— 当语法规则多了以后,解析代码可能变得复杂。

8. 解释器模式 vs 其他模式

对比点解释器模式策略模式状态模式
目的解析和执行语法规则选择不同算法让对象在不同状态下行为不同
适用场景解析语言、表达式计算运行时动态选择策略依赖状态的行为变化
复杂性适用于简单规则代码简单适用于有限状态机

9. 适用场景

场景示例
数学表达式计算解析 5 + 3 - 2 这样的表达式
规则引擎解析和执行业务规则,如促销规则
SQL 解析解析 SQL 查询,如 SELECT * FROM users WHERE age > 18
正则表达式解析并执行 ^[a-z]+@[a-z]+.com$ 这样的正则

10. 总结

✅ 核心思想:用类表示语言的语法,并提供解释器执行。 ✅ 关键点:构建语法树,递归解析表达式。 ✅ 适用场景:数学计算、规则解析、DSL 设计等。

🚀 一句话总结:解释器模式适合小型语言解析,但复杂场景建议使用编译器工具! 🎯

更多推荐