访问者模式
访问者模式(Visitor Pattern)
定义
访问者模式(Visitor Pattern)是行为型设计模式的一种,它将算法与对象结构分离,允许在不修改现有对象结构的前提下定义新的操作。该模式通过双重分派机制实现操作与对象结构的解耦。
核心思想
-
关注点分离:对象结构负责数据存储,访问者负责行为逻辑
-
双重分发:通过两次方法调用(对象接受访问者,访问者访问对象)动态绑定具体实现。
-
开放/封闭原则:新增操作时无需修改对象结构,但新增元素类型时可能需要调整访问者接口。
-
算法集中管理:相关操作集中在同一访问者中
模式结构
+------------------+ +-------------------+
| ObjectStructure| | IVisitor |
|------------------| |-------------------|
| +AddElement() | | +VisitElementA() |
| +AcceptVisitor() | | +VisitElementB() |
+------------------+ +-------------------+
| ^
| Contains | Implements
v |
+------------------+ +-------------------+
| IElement | | ConcreteVisitor |
|------------------| |-------------------|
| +Accept(IVisitor)| | +VisitElementA() |
+------------------+ | +VisitElementB() |
^ +-------------------+
| Implements
+--------+--------+
| |
| ConcreteElementA| ConcreteElementB
|-----------------| -----------------
| +Accept() | +Accept()
+-----------------+ -----------------
角色 | 职责说明 |
---|---|
IVisitor(访问者接口) | 声明访问各类具体元素的Visit方法。 |
ConcreteVisitor(具体访问者) | 实现接口,定义对具体元素的操作逻辑。 |
IElement(元素接口) | 声明接受访问者的Accept方法。 |
ConcreteElement(具体元素) | 实现Accept方法,调用访问者的对应方法。 |
ObjectStructure(对象结构) | 维护元素集合,提供遍历元素的方法。 |
主要解决的问题
-
功能扩展不破坏封装:在不修改元素类的前提下添加新操作。
-
集中相关操作:将分散在多个类中的操作聚合到访问者中。
-
复杂对象结构的统一处理:对包含多种类型元素的结构执行统一遍历和操作。
-
数据/行为耦合:分离对象数据结构与其关联的操作逻辑
代码示例
- 场景:文档处理系统,支持对文本、图片元素生成HTML和PDF格式。
// 元素接口
public interface IDocumentElement
{
void Accept(IVisitor visitor);
}
// 具体元素实现
public class TextElement : IDocumentElement
{
public string Content { get; set; }
public void Accept(IVisitor visitor) => visitor.VisitText(this);
}
public class ImageElement : IDocumentElement
{
public string Url { get; set; }
public string AltText { get; set; }
public void Accept(IVisitor visitor) => visitor.VisitImage(this);
}
public class TableElement : IDocumentElement
{
public int Rows { get; set; }
public int Columns { get; set; }
public void Accept(IVisitor visitor) => visitor.VisitTable(this);
}
// 访问者接口
public interface IVisitor
{
void VisitText(TextElement text);
void VisitImage(ImageElement image);
void VisitTable(TableElement table);
}
// 具体访问者实现
public class HtmlExportVisitor : IVisitor
{
public void VisitText(TextElement text)
{
Console.WriteLine($"<p>{text.Content}</p>");
}
public void VisitImage(ImageElement image)
{
Console.WriteLine($"<img src='{image.Url}' alt='{image.AltText}'>");
}
public void VisitTable(TableElement table)
{
Console.WriteLine($"<table><caption>{table.Rows}x{table.Columns}表格</caption></table>");
}
}
public class MarkdownExportVisitor : IVisitor
{
public void VisitText(TextElement text)
{
Console.WriteLine(text.Content + "\n");
}
public void VisitImage(ImageElement image)
{
Console.WriteLine($"");
}
public void VisitTable(TableElement table)
{
Console.WriteLine($"| {' | '.PadRight(table.Columns * 6)}|");
}
}
// 对象结构
public class Document
{
private readonly List<IDocumentElement> _elements = new();
public void AddElement(IDocumentElement element) => _elements.Add(element);
public void Accept(IVisitor visitor)
{
foreach (var element in _elements)
{
element.Accept(visitor);
}
}
}
// 客户端调用
var doc = new Document();
doc.AddElement(new TextElement { Content = "欢迎访问示例文档" });
doc.AddElement(new ImageElement { Url = "chart.png", AltText = "数据图表" });
doc.AddElement(new TableElement { Rows = 3, Columns = 4 });
Console.WriteLine("导出为HTML:");
doc.Accept(new HtmlExportVisitor());
Console.WriteLine("\n导出为Markdown:");
doc.Accept(new MarkdownExportVisitor());
总结
优点
-
高扩展性:新增操作只需添加访问者,无需修改元素类。
-
职责清晰:相关操作集中到访问者中,避免污染元素类。
-
支持复杂操作:可访问多个不同类型的元素并共享状态。
缺点
-
破坏封装性:访问者需要了解元素内部细节。
-
元素类型变更困难:新增元素类型需修改所有访问者接口。
-
增加系统复杂度:双重分发机制可能让代码更难理解。
适用场景
-
对象结构稳定但需频繁新增操作(如文件导出、格式转换)。
-
需要对复杂结构中的元素执行统一处理(如编译器语法树分析)。
-
操作逻辑需要跨多个元素类协作(如日志处理)。
注意事项
-
避免过度使用,优先考虑简单条件判断或策略模式。
-
若元素类型频繁变化,访问者模式可能不适用。