访问者模式属于行为型设计模式的一种,核心思想是将作用于某种数据结构中的各元素的操作分离出来封装成独立的访问者类,这样可以在不改变各元素类的前提下定义作用于这些元素的新操作。在Golang中实现访问者模式,需要结合接口和结构体来完成对象操作的封装。

访问者模式的核心角色
在Golang实现访问者模式时,通常包含以下几个核心角色:
- 元素接口:定义接受访问者的方法,所有需要被访问的元素都需要实现这个接口
- 具体元素:实现元素接口,存储自身的状态,同时实现接受访问者的方法,在方法中调用访问者对应的访问逻辑
- 访问者接口:定义访问不同类型具体元素的方法,每个方法对应一种具体元素类型
- 具体访问者:实现访问者接口,实现针对不同元素的具体操作逻辑
- 对象结构:管理元素集合,提供让访问者访问所有元素的方法
基础实现示例
下面以电商系统中的商品元素为例,实现访问者模式。商品分为图书和电子产品两种类型,我们需要实现两种访问者:价格计算访问者和商品信息打印访问者。
定义元素接口和具体元素
package main
import "fmt"
// 元素接口 定义接受访问者的方法
type Product interface {
Accept(visitor ProductVisitor)
}
// 图书具体元素
type Book struct {
Name string
Price float64
Author string
}
func (b *Book) Accept(visitor ProductVisitor) {
visitor.VisitBook(b)
}
// 电子产品具体元素
type Electronic struct {
Name string
Price float64
Brand string
}
func (e *Electronic) Accept(visitor ProductVisitor) {
visitor.VisitElectronic(e)
}
定义访问者接口和具体访问者
// 访问者接口 定义访问不同元素的方法
type ProductVisitor interface {
VisitBook(book *Book)
VisitElectronic(electronic *Electronic)
}
// 价格计算访问者 计算所有商品的总价
type PriceCalculatorVisitor struct {
TotalPrice float64
}
func (p *PriceCalculatorVisitor) VisitBook(book *Book) {
p.TotalPrice += book.Price
}
func (p *PriceCalculatorVisitor) VisitElectronic(electronic *Electronic) {
p.TotalPrice += electronic.Price
}
// 商品信息打印访问者 打印所有商品的信息
type InfoPrintVisitor struct{}
func (i *InfoPrintVisitor) VisitBook(book *Book) {
fmt.Printf("图书名称:%s,价格:%.2f,作者:%sn", book.Name, book.Price, book.Author)
}
func (i *InfoPrintVisitor) VisitElectronic(electronic *Electronic) {
fmt.Printf("电子产品名称:%s,价格:%.2f,品牌:%sn", electronic.Name, electronic.Price, electronic.Brand)
}
定义对象结构
// 对象结构 管理商品集合
type ProductCollection struct {
products []Product
}
func (pc *ProductCollection) AddProduct(product Product) {
pc.products = append(pc.products, product)
}
func (pc *ProductCollection) Accept(visitor ProductVisitor) {
for _, product := range pc.products {
product.Accept(visitor)
}
}
完整调用示例
func main() {
// 初始化商品集合
collection := &ProductCollection{}
collection.AddProduct(&Book{Name: "Golang编程指南", Price: 89.9, Author: "张三"})
collection.AddProduct(&Electronic{Name: "无线鼠标", Price: 129.9, Brand: "罗技"})
collection.AddProduct(&Book{Name: "数据结构与算法", Price: 79.9, Author: "李四"})
// 使用价格计算访问者
priceVisitor := &PriceCalculatorVisitor{}
collection.Accept(priceVisitor)
fmt.Printf("所有商品总价:%.2fn", priceVisitor.TotalPrice)
// 使用信息打印访问者
infoVisitor := &InfoPrintVisitor{}
collection.Accept(infoVisitor)
}
访问者模式的适用场景
访问者模式适合以下场景使用:
- 对象结构比较稳定,但经常需要在此结构上定义新的操作
- 需要对一个对象结构中的多个元素进行很多不同的且互不相关的操作,希望避免这些操作污染元素类
- 业务规则要求遍历多个不同类型的对象,并且针对不同对象执行不同的处理逻辑
注意事项
在Golang中使用访问者模式需要注意几个问题:
- 如果元素类型经常变化,访问者接口需要频繁修改,此时不适合使用访问者模式
- 访问者模式会增加类的数量,如果操作比较简单,没必要过度设计使用此模式
- Golang没有类的继承机制,通过接口实现多态,需要注意接口实现的规范性,避免类型断言错误
- 如果访问者需要访问元素的私有属性,可以在元素中提供对应的 getter 方法,或者在同包下实现访问逻辑