访问者模式的核心思想是将操作与数据结构分离,Golang通过接口、方法集和类型组合实现:定义Element接口含Accept方法,各具体元素实现该方法并调用visitor对应VisitXXX;Visitor接口声明各类Visit方法,新增功能只需实现该接口,无需修改原有结构。

访问者模式的核心思想是把“操作”从“数据结构”中分离出来,让新增功能无需改动原有结构代码。Golang 没有传统面向对象的继承和虚函数,但可以通过接口、方法集和类型组合优雅实现该模式。
定义元素接口与具体元素
先定义一个 Element 接口,要求所有可被访问的对象都实现 Accept(visitor Visitor) 方法:
type Element interface {
Accept(Visitor) // 接收访问者
}
再为每种具体数据结构(如文件、目录、链接)实现该接口。关键点是:每个 Accept 方法内部调用 visitor.VisitXXX(this),把自身作为参数传给访问者对应的方法:
- 文件类型示例:
type File struct {
Name string
Size int
}
func (f *File) Accept(v Visitor) {
v.VisitFile(f)
}
- 目录类型示例:
type Directory struct {
Name string
Children []Element
}
func (d *Directory) Accept(v Visitor) {
v.VisitDirectory(d)
}
定义访问者接口与具体访问者
定义 Visitor 接口,声明对每种元素类型的处理方法:
立即学习“go语言免费学习笔记(深入)”;
type Visitor interface {
VisitFile(*File)
VisitDirectory(*Directory)
}
新增功能只需实现这个接口,例如统计总大小的访问者:
type SizeCalculator struct {
Total int
}
func (s *SizeCalculator) VisitFile(f *File) {
s.Total += f.Size
}
func (s *SizeCalculator) VisitDirectory(d *Directory) {
// 目录本身不占空间,但需递归访问子项
for _, child := range d.Children {
child.Accept(s) // 递归分发
}
}
再比如打印路径的访问者:
type PathPrinter struct {
Prefix string
}
func (p *PathPrinter) VisitFile(f *File) {
fmt.Println(p.Prefix + "/" + f.Name)
}
func (p *PathPrinter) VisitDirectory(d *Directory) {
fmt.Println(p.Prefix + "/" + d.Name)
for _, child := range d.Children {
child.Accept(&PathPrinter{Prefix: p.Prefix + "/" + d.Name})
}
}
使用访问者遍历对象结构
客户端只需创建元素树,然后传入任意访问者实例即可执行新逻辑,完全不修改 File 或 Directory 的定义:
root := &Directory{
Name: "root",
Children: []Element{
&File{Name: "readme.txt", Size: 1024},
&Directory{
Name: "src",
Children: []Element{
&File{Name: "main.go", Size: 2048},
},
},
},
}
// 统计大小
calc := &SizeCalculator{}
root.Accept(calc)
fmt.Println("Total size:", calc.Total) // 输出 3072
// 打印路径
printer := &PathPrinter{Prefix: ""}
root.Accept(printer)
注意事项与优化建议
Golang 中实现访问者模式需注意几点:
-
接口方法名需明确区分类型:如
VisitFile和VisitDirectory,避免类型擦除后无法分发 - 访问者通常需要状态:用结构体实现,而非函数闭包,便于复用和测试
-
递归访问由访问者自己控制:比如
VisitDirectory内手动调用child.Accept(v),保持灵活性 - 若元素类型较多,可借助代码生成工具(如
stringer思路)自动生成Accept方法,减少模板代码
不复杂但容易忽略。










