访问者模式的核心价值是在不修改元素类的前提下为稳定结构添加新行为,适用于元素类型固定、操作多变的场景,如AST分析、DOM渲染、文件系统遍历;其硬约束是Visitor接口需显式声明每种元素的Visit方法,且Accept方法必须硬编码分发。
访问者模式的核心价值,不是“增加变量操作”,而是在不修改元素类的前提下,为稳定的数据结构添加新的行为或操作。所谓“变量操作”容易引起误解——它不指给对象加字段或改状态,而是指新增一类可复用、可隔离的处理逻辑,比如统计、导出、校验、渲染等。
关键前提:结构稳定,操作多变
该模式真正起效的前提是:元素类型基本固定(如 File、Directory、Image 已定义完毕),后续极少新增 ConcreteElement 子类。一旦开始频繁添加新元素(例如后来加了 Symlink、Archive、RemoteFile),Visitor 接口就得补方法,所有已有访问者也要跟着改,反而破坏了扩展性。
常见适用场景包括:
- 抽象语法树(AST)上做多种分析:类型检查、代码生成、复杂度评估
- 文档对象模型(DOM/Markdown AST)中执行不同输出:HTML 渲染、Markdown 导出、纯文本提取
- 文件系统树遍历:计算总大小、查找大文件、生成目录快照、权限审计
必须显式声明每种元素的 Visit 方法
Visitor 接口不是泛型或动态分发的。在 Java、Go 等静态语言中,它必须提前写明每个具体类型的访问入口:
-
Java 示例:
void visit(File file)、void visit(Directory dir)、void visit(Image img) -
Go 示例:
VisitFile(*File)、VisitDirectory(*Directory)
漏掉任一类型,新访问者就无法安全处理该元素;新增类型,则必须修改 Visitor 接口及全部实现类——这是模式最硬的约束,不可绕过。
Accept 方法是硬编码分发点
每个具体元素的 Accept 方法不能靠反射、字符串拼接或运行时判断来调用访问者,而必须直接写死对应调用:
func (f *File) Accept(v Visitor) { v.VisitFile(f) }func (d *Directory) Accept(v Visitor) { v.VisitDirectory(d) }
这种设计保证了调用链清晰、性能可控,但也意味着:如果某元素需被多种语义识别(如既当普通文件又当可执行文件),不能靠一个类型+条件分支解决,而应拆分为不同类型,或把语义判断前移到访问者内部(但会削弱封装性)。
新操作 = 新建 ConcreteVisitor,且只能用公开接口
添加一个“生成 Markdown”的功能,只需写一个新的 MarkdownExportVisitor,但它能访问的字段和方法,仅限元素类主动暴露的公共成员(如 Name、Size、FullPath())。
若业务需要未导出字段(如 file.contentHash),有且仅有两种选择:
- 在元素类中补充只读方法(推荐,保持封装边界)
- 修改元素类暴露字段(破坏封装,不建议)
访问者模式不解决数据可见性问题,它只解决“谁来调用、怎么分发”的问题。










