Skip to content

访问者模式

“对一组对象的各种操作”集中封装到一个独立的对象里

适用的场景

  • 数据结构稳定,但操作经常变化!(比如编译器、文档结构、语法树)

  • 想把行为和数据结构解耦(就像你想加新功能但又不想改原来的类)

当你想对一堆对象做很多不同的事,而这些对象结构不常改时,就请访问者来做客

基本结构

┌──────────────┐           ┌───────────────┐
│   Element    │<───┐      │   Visitor     │
│(被访问的东西)│    │      │(访问逻辑)    │
└─────▲────────┘    │      └────▲──────────┘
      │              │          │
┌─────┴─────┐   ┌────┴─────┐ ┌──┴──────────┐
│ConcreteA  │   │ConcreteB │ │PrintVisitor │
└───────────┘   └──────────┘ └─────────────┘
  • Element 定义一个 Accept(visitor) 方法,让访问者进来访问

  • Visitor 定义针对不同 Element 的访问方法,比如 VisitA(), VisitB()

示例

csharp
// 元素接口
interface IElement 
{
    void Accept(IVisitor visitor);
}

// 具体元素A
class File : IElement 
{
    public string Name = "File.txt";
    public void Accept(IVisitor visitor) => visitor.VisitFile(this);
}

// 具体元素B
class Folder : IElement 
{
    public string Name = "Documents";
    public void Accept(IVisitor visitor) => visitor.VisitFolder(this);
}

// 访问者接口
interface IVisitor 
{
    void VisitFile(File file);
    void VisitFolder(Folder folder);
}

// 具体访问者
class PrintVisitor : IVisitor 
{
    public void VisitFile(File file) => Console.WriteLine("访问文件: " + file.Name);
    public void VisitFolder(Folder folder) => Console.WriteLine("访问文件夹: " + folder.Name);
}

// 使用
var elements = new List<IElement> { new File(), new Folder() };
var visitor = new PrintVisitor();

foreach (var elem in elements)
    elem.Accept(visitor);

GDScript示例

gdscript
class Element:
    func accept(visitor: Visitor):
        pass

class FileElement extends Element:
    var name = "file.txt"
    func accept(visitor): visitor.visit_file(self)

class FolderElement extends Element:
    var name = "documents"
    func accept(visitor): visitor.visit_folder(self)

class Visitor:
    func visit_file(file): pass
    func visit_folder(folder): pass

class PrintVisitor extends Visitor:
    func visit_file(file): print("访问文件: ", file.name)
    func visit_folder(folder): print("访问文件夹: ", folder.name)

# 用法
func _ready():
    var elements = [FileElement.new(), FolderElement.new()]
    var visitor = PrintVisitor.new()
    for elem in elements:
        elem.accept(visitor)