访问者模式(Visitor Pattern)
大约 13 分钟约 4041 字
访问者模式(Visitor Pattern)
简介
访问者模式(Visitor Pattern)是一种行为型设计模式,它将操作(算法)从数据结构中分离出来。核心思想是:在不修改已有数据类的前提下,为数据结构中的元素新增操作。
这个模式之所以能做到"不改数据类就能加操作",关键在于双分派(Double Dispatch)机制——调用过程由两个对象的运行时类型共同决定:先由数据元素调用 Accept(visitor),再由 Visitor 根据 this 的具体类型调用对应的 Visit(ConcreteElement) 方法。
适用场景
访问者模式适用于以下特征的项目:
- 数据结构稳定:对象结构中的元素类型很少变化(如 AST 节点类型固定)
- 操作频繁变化:需要对同一组对象执行多种不同的操作(如求值、打印、优化、类型检查)
- 操作需要跨类型协作:某些操作需要同时访问多种类型的元素(如代码生成需要遍历整个 AST)
典型的应用场景包括:
- 编译器/解释器:AST 的求值、打印、优化、类型检查
- 文档处理:将文档树导出为 HTML、Markdown、PDF、Word 等格式
- 报表系统:同一份数据生成不同维度的报表
- 规则引擎:对一组规则节点执行不同的验证策略
不适用场景
- 数据结构频繁变化(每增加一个数据类都要修改所有 Visitor)
- 数据类极少且操作也固定(直接写方法即可,无需引入模式)
- 数据类的内部状态不希望暴露给外部(访问者需要访问数据类内部)
核心概念
双分派机制详解
在单分派(C# 的普通方法重载)中,方法的调用只由一个对象的运行时类型决定:
// 单分派:只有 receiver 的运行时类型决定调用哪个方法
void Process(Animal a) { }
void Process(Dog d) { }
Animal a = new Dog();
Process(a); // 调用 Process(Animal),因为编译时类型是 Animal访问者模式通过两次虚方法调用实现双分派:
// 第一次分派:element.Accept(visitor) -> 根据 element 的运行时类型
// 第二次分派:visitor.Visit(this) -> 根据 visitor 的运行时类型
element.Accept(visitor);
// 等价于: visitor.Visit((ConcreteElement)element)流程图:
客户端调用
│
▼
expr.Accept(visitor) ← 第一次分派(根据 expr 的运行时类型)
│
├── NumberExpression → visitor.Visit(this) ← 第二次分派
├── AddExpression → visitor.Visit(this)
├── MultiplyExpression → visitor.Visit(this)
└── NegateExpression → visitor.Visit(this)模式结构
访问者模式包含以下角色:
| 角色 | 说明 |
|---|---|
IVisitor | 访问者接口,为每种数据元素定义一个 Visit 方法 |
ConcreteVisitor | 具体访问者,实现具体的操作逻辑 |
IElement | 元素接口,声明 Accept 方法 |
ConcreteElement | 具体元素,实现 Accept 方法 |
ObjectStructure | 对象结构,包含元素集合,提供遍历接口 |
UML 协作流程
1. 客户端创建具体的 Visitor
2. 客户端将 Visitor 传递给数据元素的 Accept 方法
3. 数据元素调用 visitor.Visit(this),将自身传给 Visitor
4. Visitor 根据 this 的运行时类型,调用对应的重载方法
5. Visitor 执行具体操作实现一:表达式求值与打印
这是访问者模式最经典的示例——对数学表达式(AST)执行不同的操作。
数据结构定义(AST 节点)
/// <summary>
/// 抽象表达式节点
/// </summary>
public abstract class Expression
{
public abstract void Accept(IExpressionVisitor visitor);
public abstract TResult Accept<TResult>(IExpressionVisitor<TResult> visitor);
}
/// <summary>
/// 数字常量节点
/// </summary>
public class NumberExpression : Expression
{
public double Value { get; }
public NumberExpression(double value)
{
Value = value;
}
public override void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
public override TResult Accept<TResult>(IExpressionVisitor<TResult> visitor)
=> visitor.Visit(this);
}
/// <summary>
/// 二元加法节点
/// </summary>
public class AddExpression : Expression
{
public Expression Left { get; }
public Expression Right { get; }
public AddExpression(Expression left, Expression right)
{
Left = left ?? throw new ArgumentNullException(nameof(left));
Right = right ?? throw new ArgumentNullException(nameof(right));
}
public override void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
public override TResult Accept<TResult>(IExpressionVisitor<TResult> visitor)
=> visitor.Visit(this);
}
/// <summary>
/// 二元乘法节点
/// </summary>
public class MultiplyExpression : Expression
{
public Expression Left { get; }
public Expression Right { get; }
public MultiplyExpression(Expression left, Expression right)
{
Left = left ?? throw new ArgumentNullException(nameof(left));
Right = right ?? throw new ArgumentNullException(nameof(right));
}
public override void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
public override TResult Accept<TResult>(IExpressionVisitor<TResult> visitor)
=> visitor.Visit(this);
}
/// <summary>
/// 一元取反节点
/// </summary>
public class NegateExpression : Expression
{
public Expression Operand { get; }
public NegateExpression(Expression operand)
{
Operand = operand ?? throw new ArgumentNullException(nameof(operand));
}
public override void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
public override TResult Accept<TResult>(IExpressionVisitor<TResult> visitor)
=> visitor.Visit(this);
}
/// <summary>
/// 变量引用节点
/// </summary>
public class VariableExpression : Expression
{
public string Name { get; }
public VariableExpression(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public override void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
public override TResult Accept<TResult>(IExpressionVisitor<TResult> visitor)
=> visitor.Visit(this);
}访问者接口
/// <summary>
/// 表达式访问者接口(无返回值)
/// </summary>
public interface IExpressionVisitor
{
void Visit(NumberExpression expr);
void Visit(AddExpression expr);
void Visit(MultiplyExpression expr);
void Visit(NegateExpression expr);
void Visit(VariableExpression expr);
}
/// <summary>
/// 表达式访问者接口(带返回值 — 泛型版本)
/// </summary>
public interface IExpressionVisitor<TResult>
{
TResult Visit(NumberExpression expr);
TResult Visit(AddExpression expr);
TResult Visit(MultiplyExpression expr);
TResult Visit(NegateExpression expr);
TResult Visit(VariableExpression expr);
}具体访问者:求值
/// <summary>
/// 表达式求值访问者
/// </summary>
public class EvaluatorVisitor : IExpressionVisitor<double>
{
private readonly Dictionary<string, double> _variables = new();
public EvaluatorVisitor() { }
public EvaluatorVisitor(Dictionary<string, double> variables)
{
foreach (var kvp in variables)
_variables[kvp.Key] = kvp.Value;
}
public double Visit(NumberExpression expr) => expr.Value;
public double Visit(AddExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
return left + right;
}
public double Visit(MultiplyExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
return left * right;
}
public double Visit(NegateExpression expr)
{
return -expr.Operand.Accept(this);
}
public double Visit(VariableExpression expr)
{
if (_variables.TryGetValue(expr.Name, out var value))
return value;
throw new InvalidOperationException($"变量 '{expr.Name}' 未定义");
}
}具体访问者:打印
/// <summary>
/// 表达式打印访问者
/// </summary>
public class PrintVisitor : IExpressionVisitor<string>
{
public string Visit(NumberExpression expr)
{
// 整数不带小数点
return expr.Value == Math.Floor(expr.Value)
? ((int)expr.Value).ToString()
: expr.Value.ToString("G");
}
public string Visit(AddExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
return $"({left} + {right})";
}
public string Visit(MultiplyExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
return $"({left} * {right})";
}
public string Visit(NegateExpression expr)
{
var operand = expr.Operand.Accept(this);
return $"(-{operand})";
}
public string Visit(VariableExpression expr)
{
return expr.Name;
}
}具体访问者:常量折叠优化
/// <summary>
/// 常量折叠优化访问者 — 将纯常量表达式预计算
/// </summary>
public class ConstantFoldingVisitor : IExpressionVisitor<Expression>
{
public Expression Visit(NumberExpression expr) => expr;
public Expression Visit(VariableExpression expr) => expr;
public Expression Visit(AddExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
// 如果两边都是常量,直接计算
if (left is NumberExpression l && right is NumberExpression r)
return new NumberExpression(l.Value + r.Value);
// x + 0 = x
if (right is NumberExpression { Value: 0 }) return left;
if (left is NumberExpression { Value: 0 }) return right;
return new AddExpression(left, right);
}
public Expression Visit(MultiplyExpression expr)
{
var left = expr.Left.Accept(this);
var right = expr.Right.Accept(this);
if (left is NumberExpression l && right is NumberExpression r)
return new NumberExpression(l.Value * r.Value);
// x * 0 = 0
if (left is NumberExpression { Value: 0 } || right is NumberExpression { Value: 0 })
return new NumberExpression(0);
// x * 1 = x
if (right is NumberExpression { Value: 1 }) return left;
if (left is NumberExpression { Value: 1 }) return right;
return new MultiplyExpression(left, right);
}
public Expression Visit(NegateExpression expr)
{
var operand = expr.Operand.Accept(this);
if (operand is NumberExpression n)
return new NumberExpression(-n.Value);
// -(-x) = x
if (operand is NegateExpression neg)
return neg.Operand;
return new NegateExpression(operand);
}
}具体访问者:深度计算
/// <summary>
/// 表达式深度计算访问者
/// </summary>
public class DepthCalculator : IExpressionVisitor<int>
{
public int Visit(NumberExpression _) => 1;
public int Visit(VariableExpression _) => 1;
public int Visit(AddExpression expr)
=> 1 + Math.Max(expr.Left.Accept(this), expr.Right.Accept(this));
public int Visit(MultiplyExpression expr)
=> 1 + Math.Max(expr.Left.Accept(this), expr.Right.Accept(this));
public int Visit(NegateExpression expr)
=> 1 + expr.Operand.Accept(this);
}使用示例
// 构建表达式: 2 * 3 + (-x)
Expression expr = new AddExpression(
new MultiplyExpression(
new NumberExpression(2),
new NumberExpression(3)),
new NegateExpression(
new VariableExpression("x")));
// 求值(x = 1 时)
var evaluator = new EvaluatorVisitor(new Dictionary<string, double> { { "x", 1 } });
double result = expr.Accept(evaluator);
Console.WriteLine($"求值结果: {result}"); // 2*3 + (-1) = 5
// 打印
var printer = new PrintVisitor();
string printed = expr.Accept(printer);
Console.WriteLine($"表达式: {printed}"); // ((2 * 3) + (-x))
// 优化
var optimizer = new ConstantFoldingVisitor();
Expression optimized = expr.Accept(optimizer);
string optimizedStr = optimized.Accept(printer);
Console.WriteLine($"优化后: {optimizedStr}");
// 深度计算
var depthCalc = new DepthCalculator();
int depth = expr.Accept(depthCalc);
Console.WriteLine($"表达式深度: {depth}"); // 3实现二:文档多格式导出
文档节点定义
/// <summary>
/// 文档节点基类
/// </summary>
public abstract class DocumentNode
{
public abstract void Accept(IDocumentVisitor visitor);
}
/// <summary>
/// 文本节点
/// </summary>
public class TextNode : DocumentNode
{
public string Text { get; }
public TextAlignment Alignment { get; }
public TextNode(string text, TextAlignment alignment = TextAlignment.Left)
{
Text = text;
Alignment = alignment;
}
public override void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
/// <summary>
/// 图片节点
/// </summary>
public class ImageNode : DocumentNode
{
public string Src { get; }
public int Width { get; }
public int Height { get; }
public string? AltText { get; }
public ImageNode(string src, int width, int height, string? altText = null)
{
Src = src;
Width = width;
Height = height;
AltText = altText;
}
public override void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
/// <summary>
/// 表格节点
/// </summary>
public class TableNode : DocumentNode
{
public List<string> Headers { get; }
public List<List<string>> Rows { get; }
public TableNode(List<string> headers, List<List<string>> rows)
{
Headers = headers ?? throw new ArgumentNullException(nameof(headers));
Rows = rows ?? throw new ArgumentNullException(nameof(rows));
}
public override void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
/// <summary>
/// 列表节点
/// </summary>
public class ListNode : DocumentNode
{
public List<string> Items { get; }
public bool IsOrdered { get; }
public ListNode(List<string> items, bool isOrdered = false)
{
Items = items ?? throw new ArgumentNullException(nameof(items));
IsOrdered = isOrdered;
}
public override void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
public enum TextAlignment { Left, Center, Right }HTML 导出访问者
/// <summary>
/// HTML 导出访问者
/// </summary>
public class HtmlExportVisitor : IDocumentVisitor
{
private readonly StringBuilder _html = new();
private int _indentLevel;
public string GetHtml() => _html.ToString();
private string Indent => new string(' ', _indentLevel * 2);
public void Visit(TextNode node)
{
var cls = node.Alignment != TextAlignment.Left
? $" class=\"{(node.Alignment == TextAlignment.Center ? "text-center" : "text-right")}\""
: "";
_html.AppendLine($"{Indent}<p{cls}>{EscapeHtml(node.Text)}</p>");
}
public void Visit(ImageNode node)
{
var alt = !string.IsNullOrEmpty(node.AltText) ? $" alt=\"{EscapeHtml(node.AltText)}\"" : "";
_html.AppendLine($"{Indent}<img src=\"{EscapeHtml(node.Src)}\" " +
$"width=\"{node.Width}\" height=\"{node.Height}\"{alt} />");
}
public void Visit(TableNode node)
{
_html.AppendLine($"{Indent}<table>");
_indentLevel++;
// 表头
_html.AppendLine($"{Indent}<thead><tr>");
_indentLevel++;
foreach (var header in node.Headers)
_html.AppendLine($"{Indent}<th>{EscapeHtml(header)}</th>");
_indentLevel--;
_html.AppendLine($"{Indent}</tr></thead>");
// 表体
_html.AppendLine($"{Indent}<tbody>");
_indentLevel++;
foreach (var row in node.Rows)
{
_html.AppendLine($"{Indent}<tr>");
_indentLevel++;
foreach (var cell in row)
_html.AppendLine($"{Indent}<td>{EscapeHtml(cell)}</td>");
_indentLevel--;
_html.AppendLine($"{Indent}</tr>");
}
_indentLevel--;
_html.AppendLine($"{Indent}</tbody>");
_indentLevel--;
_html.AppendLine($"{Indent}</table>");
}
public void Visit(ListNode node)
{
var tag = node.IsOrdered ? "ol" : "ul";
_html.AppendLine($"{Indent}<{tag}>");
_indentLevel++;
foreach (var item in node.Items)
_html.AppendLine($"{Indent}<li>{EscapeHtml(item)}</li>");
_indentLevel--;
_html.AppendLine($"{Indent}</{tag}>");
}
private static string EscapeHtml(string text)
{
return text.Replace("&", "&")
.Replace("<", "<")
.Replace(">", ">")
.Replace("\"", """);
}
}Markdown 导出访问者
/// <summary>
/// Markdown 导出访问者
/// </summary>
public class MarkdownExportVisitor : IDocumentVisitor
{
private readonly StringBuilder _md = new();
public string GetMarkdown() => _md.ToString();
public void Visit(TextNode node)
{
var prefix = node.Alignment switch
{
TextAlignment.Center => "<div align=\"center\">\n",
TextAlignment.Right => "<div align=\"right\">\n",
_ => ""
};
var suffix = node.Alignment != TextAlignment.Left ? "\n</div>" : "";
_md.AppendLine($"{prefix}{node.Text}{suffix}");
}
public void Visit(ImageNode node)
{
var alt = !string.IsNullOrEmpty(node.AltText) ? node.AltText : "image";
_md.AppendLine($"");
}
public void Visit(TableNode node)
{
_md.AppendLine($"| {string.Join(" | ", node.Headers)} |");
_md.AppendLine($"| {string.Join(" | ", node.Headers.Select(_ => "---"))} |");
foreach (var row in node.Rows)
_md.AppendLine($"| {string.Join(" | ", row)} |");
_md.AppendLine();
}
public void Visit(ListNode node)
{
for (int i = 0; i < node.Items.Count; i++)
{
var prefix = node.IsOrdered ? $"{i + 1}. " : "- ";
_md.AppendLine($"{prefix}{node.Items[i]}");
}
_md.AppendLine();
}
}文本统计访问者
/// <summary>
/// 文档统计访问者 — 统计字数、段落数、图片数等
/// </summary>
public class DocumentStatsVisitor : IDocumentVisitor
{
public int TextNodes { get; private set; }
public int ImageNodes { get; private set; }
public int TableNodes { get; private set; }
public int ListNodes { get; private set; }
public int TotalCharacters { get; private set; }
public int TotalWords { get; private set; }
public void Visit(TextNode node)
{
TextNodes++;
TotalCharacters += node.Text.Length;
TotalWords += node.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
public void Visit(ImageNode node) => ImageNodes++;
public void Visit(TableNode node)
{
TableNodes++;
foreach (var row in node.Rows)
foreach (var cell in row)
{
TotalCharacters += cell.Length;
TotalWords += cell.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
public void Visit(ListNode node)
{
ListNodes++;
foreach (var item in node.Items)
{
TotalCharacters += item.Length;
TotalWords += item.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
public override string ToString() =>
$"文本段: {TextNodes}, 图片: {ImageNodes}, 表格: {TableNodes}, " +
$"列表: {ListNodes}, 总字符: {TotalCharacters}, 总词数: {TotalWords}";
}使用示例
// 构建文档
var document = new List<DocumentNode>
{
new TextNode("设备运行报告", TextAlignment.Center),
new TextNode("以下是本月设备运行情况的汇总。"),
new TableNode(
headers: new() { "设备编号", "设备名称", "运行时间(h)", "故障次数" },
rows: new()
{
new() { "PLC-001", "主控PLC", "720", "0" },
new() { "PLC-002", "辅助PLC", "698", "2" },
}),
new ListNode(new()
{
"每周检查 PLC 通讯状态",
"每月更换变频器冷却风扇",
}, isOrdered: false),
new ImageNode("images/chart.png", 600, 300, "故障趋势图")
};
// 导出 HTML
var htmlVisitor = new HtmlExportVisitor();
foreach (var node in document)
node.Accept(htmlVisitor);
File.WriteAllText("report.html", htmlVisitor.GetHtml());
// 导出 Markdown
var mdVisitor = new MarkdownExportVisitor();
foreach (var node in document)
node.Accept(mdVisitor);
File.WriteAllText("report.md", mdVisitor.GetMarkdown());
// 统计信息
var statsVisitor = new DocumentStatsVisitor();
foreach (var node in document)
node.Accept(statsVisitor);
Console.WriteLine(statsVisitor);实现三:反射式通用访问者
当元素类型较多或无法修改数据类时,可以使用反射简化 Visitor 实现:
/// <summary>
/// 反射式通用访问者基类
/// </summary>
public abstract class ReflectionVisitor
{
public void Visit(object element)
{
var method = GetType().GetMethod("Visit", new[] { element.GetType() });
if (method != null)
method.Invoke(this, new[] { element });
else
DefaultVisit(element);
}
protected virtual void DefaultVisit(object element)
{
Debug.WriteLine($"未处理的类型: {element.GetType().Name}");
}
}
// 使用
public class LoggingVisitor : ReflectionVisitor
{
public void Visit(TextNode node)
=> Debug.WriteLine($"[LOG] Text: {node.Text}");
public void Visit(ImageNode node)
=> Debug.WriteLine($"[LOG] Image: {node.Src}");
public void Visit(TableNode node)
=> Debug.WriteLine($"[LOG] Table: {node.Headers.Count} columns");
}性能考虑
访问者的性能开销
访问者模式引入了额外的虚方法调用和对象创建开销。在性能敏感的场景(如编译器前端、高频规则评估)中,需要注意:
// 不好:每次求值都创建新的 Visitor
double Evaluate(Expression expr)
{
var visitor = new EvaluatorVisitor();
return expr.Accept(visitor);
}
// 好:复用 Visitor
private readonly EvaluatorVisitor _evaluator = new();
double Evaluate(Expression expr) => expr.Accept(_evaluator);缓存访问结果
对于不变的表达式,可以缓存求值结果:
public class CachedEvaluatorVisitor : IExpressionVisitor<double>
{
private readonly Dictionary<Expression, double> _cache = new();
private readonly EvaluatorVisitor _evaluator = new();
public double Visit(NumberExpression expr)
{
if (!_cache.TryGetValue(expr, out var value))
{
value = _evaluator.Visit(expr);
_cache[expr] = value;
}
return value;
}
// ... 其他 Visit 方法类似
}优缺点总结
优点
- 开闭原则(OCP):新增操作只需添加新的 Visitor 类,无需修改数据类
- 单一职责(SRP):相关操作集中在一个 Visitor 类中,职责清晰
- 灵活组合:可以自由组合不同的操作,如先优化再求值
- 跨类型协作:一个 Visitor 可以访问多种类型的内部数据
- 双分派:运行时正确匹配类型,避免大量的 if-else 或 switch
- 跨模块扩展:可以在不同模块中独立添加新的 Visitor
缺点
- 逆开闭原则:新增数据类需要修改所有 Visitor 接口和实现
- 封装破坏:Visitor 需要访问数据类的内部状态,违反了封装原则
- 复杂度高:双分派机制不易理解,增加了团队学习成本
- 类膨胀:每种操作都需要一个 Visitor 类,类数量增长较快
- 调试困难:调用链经过两次分派,堆栈跟踪不够直观
与其他模式的对比
| 特性 | 访问者模式 | 策略模式 | 命令模式 |
|---|---|---|---|
| 核心目的 | 对固定结构增加操作 | 封装可替换的算法 | 封装请求为对象 |
| 变化维度 | 操作变化 | 算法变化 | 请求变化 |
| 适用结构 | 稳定的对象结构 | 单一上下文 | 命令队列/撤销 |
| 双分派 | 支持 | 不支持 | 不支持 |
适用场景判断
适合使用访问者模式的场景
- 数据结构稳定,操作频繁变化(如 AST 节点类型固定,但需要不断添加新的分析/转换/优化操作)
- 需要对一组不同类型的对象执行不相关的操作(如报表系统需要对不同类型的数据进行统计、导出、校验)
- 操作需要访问对象结构的内部细节(如代码生成器需要了解每个 AST 节点的所有属性)
不适合使用访问者模式的场景
- 数据结构频繁变化(每新增一个数据类都要修改所有 Visitor)
- 数据类型很少,操作也很少(只有 2-3 种类型和 1-2 种操作,简单的 if-else 更直接)
- 数据类需要严格封装(不希望外部代码访问内部属性)
实际项目中的落地建议
- 先画结构图:在引入访问者模式之前,先画出参与对象、依赖方向和调用链
- 评估数据结构稳定性:确认数据类不会频繁新增,否则维护成本很高
- 提供泛型返回值接口:使用
IVisitor<TResult>接口,让访问者有返回值 - 编写单元测试:为每个 Visitor 编写独立的测试用例,确保各种组合情况正确
- 考虑反射替代方案:如果数据类不可修改,使用反射实现访问
- 文档化 Visitor 的职责:每个 Visitor 类应有明确的文档说明其操作语义
- 考虑替代方案:如果数据类和操作都比较稳定,直接写方法可能更简单
常见误区
- 为了看起来"高级"而套模式:如果只是两三个数据类和一种操作,直接写方法更清晰
- 数据类频繁变化时使用访问者:每新增一个数据类都要修改所有 Visitor,维护成本很高
- Visitor 中包含业务状态:Visitor 应该是无状态的或轻量级状态,复杂状态应放在外部
- 过度嵌套的 Accept 调用:深层次的递归调用可能导致栈溢出,需要考虑迭代实现
- 只会背 UML,不会解释为什么:模式不是目标,降低耦合和控制变化才是目标
