访问者模式的核心是将操作逻辑从对象内部抽离至独立的访问者类,前提是元素结构稳定且visitor接口按元素类型命名(如visitfile、visitdirectory)并预先覆盖所有类型,新增功能只需添加concretevisitor实现,无需修改现有类。
核心是把操作逻辑从对象内部抽出来,放到独立的访问者类里。只要对象结构稳定、访问者接口提前覆盖所有类型,新增功能就只需加一个新访问者,不用碰任何已有类。
前提:元素结构必须稳定且接口定义完整
访问者模式只在结构基本定型后才真正省事。比如文件系统里已有 File 和 Directory,后续不打算加 Symlink 或 HardLink,那就可以放心用。如果未来可能新增元素类型,Visitor 接口得提前声明对应方法(哪怕先空实现),否则一加新类型就得改接口——所有老访问者都得同步补方法,反而更麻烦。
关键:Visitor 接口按元素类型命名,不按操作意图
接口方法名要体现“谁被访问”,而不是“要干什么”。例如写 VisitFile(*File) 和 VisitDirectory(*Directory),而不是 VisitForExport() 或 VisitForBackup()。前者让接口稳定,后者会让每次业务变化都倒逼接口改名或加方法。
- Go 中定义为:type Visitor interface { VisitFile(*File); VisitDirectory(*Directory) }
- Java 中类似:interface Visitor { void visit(File f); void visit(Directory d); }
- 方法签名一旦确定,就不能轻易增删改——这是整个模式能“只加不改”的基础
新增操作:只加 ConcreteVisitor,不改任何 Element
比如现在要支持导出 Markdown 目录树,已有 File 和 Directory 的 Accept 方法已固定调用 v.VisitFile(f) 和 v.VisitDirectory(d)。你只需新建一个结构体实现 Visitor 接口:
- 定义 MarkdownVisitor 类型,带缓冲区和缩进状态
- 实现 VisitFile:拼接
- [name]行 - 实现 VisitDirectory:写目录标题,递归处理子节点
- 调用时传入该实例,遍历过程自动分发到对应方法
注意边界:访问者只能用元素公开的字段和方法
它不是万能胶。如果 File 内部有个未导出的 contentHash,而 Markdown 导出又需要它,那就不能靠访问者硬取——得在 File 上加个公开的 Hash() 方法,或者把计算逻辑移到别处。访问者模式解决的是“调用入口”的解耦,不是封装边界的突破。设计初期就要想清楚:哪些信息该暴露,哪些该藏进方法里。










