​​访问者模式

访问者模式(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($"![{image.AltText}]({image.Url})");
    }

    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());

总结

优点

  • 高扩展性:新增操作只需添加访问者,无需修改元素类。

  • 职责清晰:相关操作集中到访问者中,避免污染元素类。

  • 支持复杂操作:可访问多个不同类型的元素并共享状态。

缺点

  • 破坏封装性:访问者需要了解元素内部细节。

  • 元素类型变更困难:新增元素类型需修改所有访问者接口。

  • 增加系统复杂度:双重分发机制可能让代码更难理解。

适用场景

  • 对象结构稳定但需频繁新增操作(如文件导出、格式转换)。

  • 需要对复杂结构中的元素执行统一处理(如编译器语法树分析)。

  • 操作逻辑需要跨多个元素类协作(如日志处理)。

注意事项

  • 避免过度使用,优先考虑简单条件判断或策略模式。

  • 若元素类型频繁变化,访问者模式可能不适用。

posted @ 2025-04-27 12:41  刘继先  阅读(25)  评论(0)    收藏  举报