访问者模式通过分离算法与对象结构实现操作扩展,Go用接口和类型断言实现:元素实现Accept方法,访问者实现对应Visit方法,新增操作无需修改元素类。

访问者模式的核心思想是把算法从对象结构中分离出来,让新增操作无需改动现有类。Golang虽无传统面向对象的继承与虚函数,但通过接口、方法集和类型断言,完全可以优雅实现访问者模式。
定义元素接口与具体元素
所有被访问的对象需实现统一接口,暴露 Accept 方法,接收访问者并调用其对应 Visit 方法:
type Element interface {
Accept(v Visitor)
}
type Book struct {
Title string
Price float64
}
func (b *Book) Accept(v Visitor) {
v.VisitBook(b)
}
type DVD struct {
Name string
Duration int
}
func (d *DVD) Accept(v Visitor) {
v.VisitDVD(d)
}
定义访问者接口及具体实现
访问者接口声明一组 Visit 方法,每个方法对应一种元素类型。不同访问者可实现不同行为:
type Visitor interface {
VisitBook(*Book)
VisitDVD(*DVD)
}
type PriceCalculator struct {
Total float64
}
func (p *PriceCalculator) VisitBook(b *Book) {
p.Total += b.Price
}
func (p *PriceCalculator) VisitDVD(d *DVD) {
p.Total += 20.0 // 假设DVD统一计价
}
type InventoryReporter struct{}
func (r *InventoryReporter) VisitBook(b *Book) {
println("Book:", b.Title)
}
func (r *InventoryReporter) VisitDVD(d *DVD) {
println("DVD:", d.Name)
}
使用访问者遍历对象集合
客户端只需持有元素切片和访问者实例,调用每个元素的 Accept 即可触发对应 Visit 方法:
立即学习“go语言免费学习笔记(深入)”;
func main() {
items := []Element{
&Book{Title: "Go Programming", Price: 49.99},
&DVD{Name: "Golang Crash Course", Duration: 120},
&Book{Title: "Design Patterns", Price: 59.99},
}
calc := &PriceCalculator{}
for _, item := range items {
item.Accept(calc)
}
fmt.Printf("Total price: $%.2f\n", calc.Total) // 输出: $109.98
reporter := &InventoryReporter{}
for _, item := range items {
item.Accept(reporter)
}
}
扩展性与注意事项
新增功能只需添加新访问者类型,不修改 Book、DVD 等结构体;若新增元素类型(如 CD),则需在 Element 接口和所有已有访问者中补充对应方法 —— 这是访问者模式的典型权衡:易扩展操作,难扩展结构。
- 推荐将
Visitor接口定义在元素包之外(如visitor/),降低耦合 - 若元素类型较多,可用代码生成工具(如
stringer风格)自动生成Accept方法 - 避免在
Visit方法中修改元素状态,保持访问逻辑纯粹










