Go设计模式之访问者模式

123 阅读3分钟

在许多情况下,我们需要访问并处理具有不同结构的对象集合。当然,可以使用面向对象编程语言的继承机制来实现这个功能,但是这种方式通常很难解耦,因为您将会得到一堆类,其继承关系非常复杂并且难以维护。访问者模式可以解决这些问题。

访问者模式是一种将算法与其所操作对象分离的方式。简单来说,访问者模式允许您为一组类定义附加行为,而无需在每个类中添加特定行为。访问者模式还能够在执行不同的操作时保持类结构不变,同时让操作和数据分离。

在本文中,我们将学习访问者模式的实现以及如何在一个假设的店铺管理系统中使用它。

实现

访问者模式包含访问者和元素两个主要组件。访问者是一个处理元素的类,而元素是一个带有“接受”方法的对象。下面是访问者接口的定义:

type Visitor interface {
    VisitBook(book *Book)
    VisitMagazine(magazine *Magazine)
}

我们需要为所有商品类型定义两个元素类。在我们的示例中,这两个类是“Book”和“Magazine”:

type Book struct {
    name   string
    author string
    price  float64
}

func (b *Book) Accept(v Visitor) {
    v.VisitBook(b)
}

type Magazine struct {
    name   string
    author string
    price  float64
}

func (m *Magazine) Accept(v Visitor) {
    v.VisitMagazine(m)
}

注意上面的代码中,我们为每个元素类型定义了一个“Accept”方法。这个方法接受一个访问者,并将当前元素传递给访问者的相应方法。

现在,我们需要定义两个具体的访问者,一个用于更新价格(PriceVisitor),另一个用于输出价格(PrintVisitor):

type PriceVisitor struct{}

func (v *PriceVisitor) VisitBook(book *Book) {
    book.price *= 2 // 将价格乘以2
}

func (v *PriceVisitor) VisitMagazine(magazine *Magazine) {
    magazine.price *= 1.5 // 将价格乘以1.5
}

type PrintVisitor struct{}

func (v *PrintVisitor) VisitBook(book *Book) {
    fmt.Printf("Book: Name=%s, Author=%s, Price=%.2f\n", book.name, book.author, book.price)
}

func (v *PrintVisitor) VisitMagazine(magazine *Magazine) {
    fmt.Printf("Magazine: Name=%s, Author=%s, Price=%.2f\n", magazine.name, magazine.author, magazine.price)
}

PriceVisitor类为每个商品类型定义了特定的访问方法。在这个示例中,我们为书籍的价格乘以2,在杂志的价格上乘以1.5。PrintVisitor类仅仅打印出当前商品的名称、作者和价格。

现在,我们可以将我们的商品放入集合中,并在这些商品上执行不同的访问操作:

func main() {
   books := []*Book{
      {name: "The Art of Computer Programming", author: "Donald Knuth", price: 100},
      {name: "The Go Programming Language", author: "Alan A. A. Donovan, Brian W. Kernighan", price: 50},
   }
   magazines := []*Magazine{
      {name: "The Go Gopher", author: "The Go Team", price: 10},
      {name: "The Go Times", author: "The Go Team", price: 5},
   }

   // 创建访问者
   priceVisitor := &PriceVisitor{}
   printVisitor := &PrintVisitor{}

   // 访问者访问元素
   for _, book := range books {
      book.Accept(priceVisitor)
      book.Accept(printVisitor)
   }

   for _, magazine := range magazines {
      magazine.Accept(priceVisitor)
      magazine.Accept(printVisitor)
   }
}

完整代码

package main

import "fmt"

type Visitor interface {
   VisitBook(book *Book)
   VisitMagazine(magazine *Magazine)
}

type Book struct {
   name   string
   author string
   price  float64
}

func (b *Book) Accept(v Visitor) {
   v.VisitBook(b)
}

type Magazine struct {
   name   string
   author string
   price  float64
}

func (m *Magazine) Accept(v Visitor) {
   v.VisitMagazine(m)
}

type PriceVisitor struct{}

func (v *PriceVisitor) VisitBook(book *Book) {
   book.price *= 2 // 将价格乘以2
}

func (v *PriceVisitor) VisitMagazine(magazine *Magazine) {
   magazine.price *= 1.5 // 将价格乘以1.5
}

type PrintVisitor struct{}

func (v *PrintVisitor) VisitBook(book *Book) {
   fmt.Printf("Book: Name=%s, Author=%s, Price=%.2f\n", book.name, book.author, book.price)
}

func (v *PrintVisitor) VisitMagazine(magazine *Magazine) {
   fmt.Printf("Magazine: Name=%s, Author=%s, Price=%.2f\n", magazine.name, magazine.author, magazine.price)
}

func main() {
   books := []*Book{
      {name: "The Art of Computer Programming", author: "Donald Knuth", price: 100},
      {name: "The Go Programming Language", author: "Alan A. A. Donovan, Brian W. Kernighan", price: 50},
   }
   magazines := []*Magazine{
      {name: "The Go Gopher", author: "The Go Team", price: 10},
      {name: "The Go Times", author: "The Go Team", price: 5},
   }

   // 创建访问者
   priceVisitor := &PriceVisitor{}
   printVisitor := &PrintVisitor{}

   // 访问者访问元素
   for _, book := range books {
      book.Accept(priceVisitor)
      book.Accept(printVisitor)
   }

   for _, magazine := range magazines {
      magazine.Accept(priceVisitor)
      magazine.Accept(printVisitor)
   }
}