在Go语言开发中,我们经常会遇到包含多种节点类型的复杂数据结构,例如表达式树、配置树或混合类型的集合。如果把这些数据的处理逻辑直接写进结构体方法里,每加一种操作就要改动所有结构体,维护成本很高。访问者模式能够将数据结构与作用于结构的操作分离,使新增功能变得简单。
什么是访问者模式
访问者模式是一种行为设计模式。它定义一个表示操作的访问者接口,让每个数据结构元素都接受一个访问者,并把自身传给访问者的方法。这样,对同一组元素可以定义不同的访问者来完成不同任务,而不用修改元素本身。
Golang中的实现思路
在Golang里没有继承,但可以用接口和类型断言模拟。核心是两个接口:元素接口提供Accept方法,访问者接口针对每种具体元素定义Visit方法。
定义元素与访问者接口
package main
import "fmt"
// 元素接口
type Element interface {
Accept(v Visitor)
}
// 访问者接口
type Visitor interface {
VisitInt(*IntElement)
VisitStr(*StrElement)
}
// 具体元素:整型
type IntElement struct {
Value int
}
func (e *IntElement) Accept(v Visitor) {
v.VisitInt(e)
}
// 具体元素:字符串
type StrElement struct {
Value string
}
func (e *StrElement) Accept(v Visitor) {
v.VisitStr(e)
}
实现具体访问者
下面写一个打印访问者,遍历复杂结构时输出每个节点内容。
// 打印访问者
type PrintVisitor struct{}
func (p *PrintVisitor) VisitInt(e *IntElement) {
fmt.Println("int:", e.Value)
}
func (p *PrintVisitor) VisitStr(e *StrElement) {
fmt.Println("str:", e.Value)
}
func main() {
// 模拟复杂数据结构:混合元素切片
elements := []Element{
&IntElement{Value: 10},
&StrElement{Value: "hello"},
&IntElement{Value: 20},
}
pv := &PrintVisitor{}
for _, e := range elements {
e.Accept(pv)
}
}
处理嵌套复杂结构
如果数据结构是树状的,可以让容器元素在Accept中递归调用子元素的Accept,访问者不用关心遍历细节。
// 容器元素
type ListElement struct {
Children []Element
}
func (l *ListElement) Accept(v Visitor) {
for _, c := range l.Children {
c.Accept(v)
}
}
此时访问者接口不用改动,只要容器元素实现Accept并遍历子节点,就能被同一批访问者处理。
适用场景与注意点
- 适合元素类型稳定、但操作经常变化的系统,例如多种报表导出。
- 如果元素类型频繁增加,访问者接口要改很多方法,反而不方便。
- Go里用类型断言或type switch也能做类似事,但访问者让结构更清晰。
访问者模式把"做什么"从"数据是什么"中抽离,是应对复杂数据结构多操作需求的有效手段。
小结
在Golang中使用访问者模式,关键是通过Element和Visitor两个接口解耦。实际项目中,可先抽象出节点类型,再按需实现不同访问者,避免业务逻辑散落在各处,提升代码可读性和扩展性。