ToB企服应用市场:ToB评测及商务社交产业平台

标题: 实践GoF的设计模式:访问者模式 [打印本页]

作者: 种地    时间: 2022-10-8 14:58
标题: 实践GoF的设计模式:访问者模式
摘要:访问者模式的目的是,解耦数据结构和算法,使得系统能够在不改变现有代码结构的基础上,为对象新增一种新的操作。
本文分享自华为云社区《【Go实现】实践GoF的23种设计模式:访问者模式》,作者:元闰子 。
简介

GoF 对访问者模式(Visitor Pattern)的定义如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
访问者模式的目的是,解耦数据结构和算法,使得系统能够在不改变现有代码结构的基础上,为对象新增一种新的操作。
上一篇介绍的迭代器模式也做到了数据结构和算法的解耦,不过它专注于遍历算法。访问者模式,则在遍历的同时,将操作作用到数据结构上,一个常见的应用场景是语法树的解析。
UML 结构

场景上下文

在 简单的分布式应用系统(示例代码工程)中,db 模块用来存储服务注册和监控信息,它是一个 key-value 数据库。另外,我们给 db 模块抽象出 Table 对象:
  1. // demo/db/table.go
  2. package db
  3. // Table 数据表定义
  4. type Table struct {
  5.     name            string
  6.     metadata        map[string]int // key为属性名,value属性值的索引, 对应到record上存储
  7.     records         map[interface{}]record
  8. iteratorFactory TableIteratorFactory // 默认使用随机迭代器
  9. }
复制代码
目的是提供类似于关系型数据库的按列查询能力,比如:
上述的按列查询只是等值比较,未来还可能会实现正则表达式匹配等方式,因此我们需要设计出可供未来扩展的接口。这种场景,使用访问者模式正合适。
代码实现
  1. // demo/db/table_visitor.go
  2. package db
  3. // 关键点1: 定义表查询的访问者抽象接口,允许后续扩展查询方式
  4. type TableVisitor interface {
  5. // 关键点2: Visit方法以Element作为入参,这里的Element为Table对象
  6. Visit(table *Table) ([]interface{}, error)
  7. }
  8. // 关键点3: 定义Visitor抽象接口的实现对象,这里FieldEqVisitor实现按列等值查询逻辑
  9. type FieldEqVisitor struct {
  10.     field string
  11.     value interface{}
  12. }
  13. // 关键点4: 为FieldEqVisitor定义Visit方法,实现具体的等值查询逻辑
  14. func (f *FieldEqVisitor) Visit(table *Table) ([]interface{}, error) {
  15. result := make([]interface{}, 0)
  16. idx, ok := table.metadata[f.field]
  17. if !ok {
  18. return nil, ErrRecordNotFound
  19. }
  20. for _, r := range table.records {
  21. if reflect.DeepEqual(r.values[idx], f.value) {
  22.             result = append(result, r)
  23. }
  24. }
  25. if len(result) == 0 {
  26. return nil, ErrRecordNotFound
  27. }
  28. return result, nil
  29. }
  30. func NewFieldEqVisitor(field string, value interface{}) *FieldEqVisitor {
  31. return &FieldEqVisitor{
  32.         field: field,
  33.         value: value,
  34. }
  35. }
  36. // demo/db/table.go
  37. package db
  38. type Table struct {...}
  39. // 关键点5: 为Element定义Accept方法,入参为Visitor接口
  40. func (t *Table) Accept(visitor TableVisitor) ([]interface{}, error) {
  41. return visitor.Visit(t)
  42. }
复制代码
客户端可以这么使用:
  1. func client() {
  2. table := NewTable("testRegion").WithType(reflect.TypeOf(new(testRegion)))
  3. table.Insert(1, &testRegion{Id: 1, Name: "beijing"})
  4. table.Insert(2, &testRegion{Id: 2, Name: "beijing"})
  5. table.Insert(3, &testRegion{Id: 3, Name: "guangdong"})
  6. visitor := NewFieldEqVisitor("name", "beijing")
  7.     result, err := table.Accept(visitor)
  8. if err != nil {
  9. t.Error(err)
  10. }
  11. if len(result) != 2 {
  12. t.Errorf("visit failed, want 2, got %d", len(result))
  13. }
  14. }
复制代码
总结实现访问者模式的几个关键点:
扩展

Go 风格实现

上述实现是典型的面向对象风格,下面以 Go 风格重新实现访问者模式:
  1. // demo/db/table_visitor_func.go
  2. package db
  3. // 关键点1: 定义一个访问者函数类型
  4. type TableVisitorFunc func(table *Table) ([]interface{}, error)
  5. // 关键点2: 定义工厂方法,工厂方法返回的是一个访问者函数,实现了具体的访问逻辑
  6. func NewFieldEqVisitorFunc(field string, value interface{}) TableVisitorFunc {
  7. return func(table *Table) ([]interface{}, error) {
  8. result := make([]interface{}, 0)
  9. idx, ok := table.metadata[field]
  10. if !ok {
  11. return nil, ErrRecordNotFound
  12. }
  13. for _, r := range table.records {
  14. if reflect.DeepEqual(r.values[idx], value) {
  15.                 result = append(result, r)
  16. }
  17. }
  18. if len(result) == 0 {
  19. return nil, ErrRecordNotFound
  20. }
  21. return result, nil
  22. }
  23. }
  24. // 关键点3: 为Element定义Accept方法,入参为Visitor函数类型
  25. func (t *Table) AcceptFunc(visitorFunc TableVisitorFunc) ([]interface{}, error) {
  26. return visitorFunc(t)
  27. }
复制代码
客户端可以这么使用:
  1. func client() {
  2. table := NewTable("testRegion").WithType(reflect.TypeOf(new(testRegion)))
  3. table.Insert(1, &testRegion{Id: 1, Name: "beijing"})
  4. table.Insert(2, &testRegion{Id: 2, Name: "beijing"})
  5. table.Insert(3, &testRegion{Id: 3, Name: "guangdong"})
  6.     result, err := table.AcceptFunc(NewFieldEqVisitorFunc("name", "beijing"))
  7. if err != nil {
  8. t.Error(err)
  9. }
  10. if len(result) != 2 {
  11. t.Errorf("visit failed, want 2, got %d", len(result))
  12. }
  13. }
复制代码
Go 风格的实现,利用了函数闭包的特点,更加简洁了。
总结几个实现关键点:
与迭代器模式结合

访问者模式经常与迭代器模式一起使用。比如上述例子中,如果你定义的 Visitor 实现不在 db 包内,那么就无法直接访问 Table 的数据,这时就需要通过 Table 提供的迭代器来实现。
在 简单的分布式应用系统(示例代码工程)中,db 模块存储的服务注册信息如下:
  1. // demo/service/registry/model/service_profile.go
  2. package model
  3. // ServiceProfileRecord 存储在数据库里的类型
  4. type ServiceProfileRecord struct {
  5.     Id       string // 服务ID
  6.     Type     ServiceType // 服务类型
  7.     Status   ServiceStatus // 服务状态
  8.     Ip       string // 服务IP
  9.     Port     int // 服务端口
  10. RegionId string // 服务所属regionId
  11.     Priority int // 服务优先级,范围0~100,值越低,优先级越高
  12.     Load     int // 服务负载,负载越高表示服务处理的业务压力越大
  13. }
复制代码
现在,我们要查询符合指定 ServiceId 和 ServiceType 的服务记录,可以这么实现一个 Visitor:
  1. // demo/service/registry/model/service_profile.go
  2. package model
  3. type ServiceProfileVisitor struct {
  4. svcId string
  5. svcType ServiceType
  6. }
  7. func (s *ServiceProfileVisitor) Visit(table *db.Table) ([]interface{}, error) {
  8. var result []interface{}
  9. // 通过迭代器来遍历Table的所有数据
  10. iter := table.Iterator()
  11. for iter.HasNext() {
  12. profile := new(ServiceProfileRecord)
  13. if err := iter.Next(profile); err != nil {
  14. return nil, err
  15. }
  16. // 先匹配ServiceId,如果一致则无须匹配ServiceType
  17. if profile.Id != "" && profile.Id == s.svcId {
  18.             result = append(result, profile)
  19. continue
  20. }
  21. // ServiceId匹配不上,再匹配ServiceType
  22. if profile.Type != "" && profile.Type == s.svcType {
  23.             result = append(result, profile)
  24. }
  25. }
  26. return result, nil
  27. }
复制代码
典型应用场景

优缺点

优点

缺点

与其他模式的关联

文章配图

可以在 用Keynote画出手绘风格的配图 中找到文章的绘图方法。
参考

[1] 【Go实现】实践GoF的23种设计模式:SOLID原则, 元闰子
[2] 【Go实现】实践GoF的23种设计模式:迭代器模式, 元闰子
[3] Design Patterns, Chapter 5. Behavioral Patterns, GoF
[4] GO 编程模式:K8S VISITOR 模式, 酷壳
[5] 访问者模式refactoringguru.cn
 
点击关注,第一时间了解华为云新鲜技术~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4