17370845950

如何使用Golang实现访问者模式_在不修改对象结构下增加功能
访问者模式通过分离算法与对象结构实现操作扩展,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 方法:

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)
    }
}

扩展性与注意事项

新增功能只需添加新访问者类型,不修改 BookDVD 等结构体;若新增元素类型(如 CD),则需在 Element 接口和所有已有访问者中补充对应方法 —— 这是访问者模式的典型权衡:易扩展操作,难扩展结构。

  • 推荐将 Visitor 接口定义在元素包之外(如 visitor/),降低耦合
  • 若元素类型较多,可用代码生成工具(如 stringer 风格)自动生成 Accept 方法
  • 避免在 Visit 方法中修改元素状态,保持访问逻辑纯粹