访客模式也叫访问者模式(Visitor Pattern)是一种将数据结构对象与数据操作分离的设计模式,可以在不改变数据结构对象类结构的前提下定义作用于这些对象的新的操作, 属于行为型设计模式。
访问者模式主要适用于以下应用场景:
- 数据结构稳定,作用于数据结构的操作经常变化的场景。
- 需要数据结构与数据操作分离的场景。
- 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。
访客模式怎么工作?
访问者模式通过将算法与对象结构分离来工作,这里说的算法指的是对对象的操作。为此,我们需要定义了一个表示算法的接口--Visitor。该接口将为对象结构中的每个类(一般称为元素类)提供一个方法。每个方法都将元素类的一个实例作为参数。表示对象结构的所有元素类也会实现一个Element接口,该接口定义了接受访问者的方法Accpet。此方法将访问者接口的实现作为参数。当Accpet方法被调用时,访问者实例对应的方法就会被调用,通过访问者完成对元素类实例的操作。
下面我们看一下访问者模式的类结构。
访客模式结构
访问者的类结构可以用下面的UML类图来表示:
- 访客接口 (Visitor) 声明了一系列以表示对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
- 具体访客 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
- 元素 (Element) 接口,声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
- 具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访客对象中的合适方法。
访客模式代码示例
在这个用访客模式实现不同维度的订单统计的例子里,假设我们建设了一个订单管理系统, 现在系统中要求能按照不同维度统计分析销售订单
- 区域销售报表: 需按销售区域, 统计销售情况
- 品类销售报表: 需根据不同产品, 统计销售情况
以后还有可能增加其他维度的销售统计报表,针对这个需求我们可以根据访问者模式, 可将不同的报表, 设计为订单的访问者。 首先定义订单实体和它要实现的Element接口
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
// 订单服务接口
type IOrderService interface {
Save(order *Order) error
// 有的教程里把接收 visitor 实现的方法名定义成 Accept
Accept(visitor IOrderVisitor)
}
// 订单实体类,实现IOrderService 接口
type Order struct {
ID int
Customer string
City string
Product string
Quantity int
}
func (mo *OrderService) Save(o *Order) error {
mo.orders[o.ID] = o
return nil
}
func (mo *OrderService) Accept(visitor IOrderVisitor) {
for _, v := range mo.orders {
visitor.Visit(v)
}
}
func NewOrder(id int, customer string, city string, product string, quantity int) *Order {
return &Order{
id, customer,city,product,quantity,
}
}
接下来定义生成各种销售报表的访客类,以及它们实现的访客接口
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type IOrderVisitor interface {
// 这里参数不能定义成 IOrderService
Visit(order *Order)
Report()
}
type CityVisitor struct {
cities map[string]int
}
func (cv *CityVisitor) Visit(o *Order) {
n, ok := cv.cities[o.City]
if ok {
cv.cities[o.City] = n + o.Quantity
} else {
cv.cities[o.City] = o.Quantity
}
}
func (cv *CityVisitor) Report() {
for k,v := range cv.cities {
fmt.Printf("city=%s, sum=%v\n", k, v)
}
}
func NewCityVisitor() IOrderVisitor {
return &CityVisitor{
cities: make(map[string]int, 0),
}
}
// 品类销售报表, 按产品汇总销售情况, 实现ISaleOrderVisitor接口
type ProductVisitor struct {
products map[string]int
}
func (pv *ProductVisitor) Visit(it *Order) {
n,ok := pv.products[it.Product]
if ok {
pv.products[it.Product] = n + it.Quantity
} else {
pv.products[it.Product] = it.Quantity
}
}
func (pv *ProductVisitor) Report() {
for k,v := range pv.products {
fmt.Printf("product=%s, sum=%v\n", k, v)
}
}
func NewProductVisitor() IOrderVisitor {
return &ProductVisitor{
products: make(map[string]int,0),
}
}
最后我们尝试使用Vistor生成各种销售报表
func main() {
orderService := NewOrderService()
orderService.Save(NewOrder(1, "张三", "广州", "电视", 10))
orderService.Save(NewOrder(2, "李四", "深圳", "冰箱", 20))
orderService.Save(NewOrder(3, "王五", "东莞", "空调", 30))
orderService.Save(NewOrder(4, "张三三", "广州", "空调", 10))
orderService.Save(NewOrder(5, "李四四", "深圳", "电视", 20))
orderService.Save(NewOrder(6, "王五五", "东莞", "冰箱", 30))
cv := NewCityVisitor()
orderService.Accept(cv)
cv.Report()
pv := NewProductVisitor()
orderService.Accept(pv)
pv.Report()
}
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341