从kubectl学Visitor模式:如何优雅处理多态数据结构的遍历

AI编程·六月创作之星博客挑战赛 10w+人浏览 1.5k人参与

前言

上个月开发一个配置同步工具,需要从多种数据源读取配置(本地文件、HTTP接口、Git仓库),然后执行一系列处理(验证格式、解密敏感信息、过滤无效配置),最后推送到服务端。

我一开始用if-else判断数据源类型:

if source.Type == "file" {
    // 读取文件...
} else if source.Type == "http" {
    // 请求HTTP...
} else if source.Type == "git" {
    // 拉取Git...
}

代码很快变成了难以维护的"意大利面条"。更痛苦的是,每次新增一种处理方式,都要修改好几个地方的代码。

后来看了kubectl的源码,发现它用Visitor模式处理这个问题——无论是文件、URL还是stdin,都用统一的Visit()方法遍历处理。新增数据源或处理逻辑时,完全不需要改动原有代码。

今天我就结合kubectl的真实实现,聊聊这个在数据处理场景中超实用的设计模式。

什么是Visitor模式?

经典定义

Visitor模式(访问者模式)是一种行为型设计模式,它的核心思想是:

将数据结构的遍历与对元素的操作分离,使得可以在不改变数据结构的前提下定义作用于这些元素的新操作。

用大白话说就是:数据是数据,操作是操作,两者解耦

为什么需要Visitor模式?

想象这样一个场景:你有一个异构数据集合,包含多种类型的元素,需要对这些元素执行各种操作。

不用Visitor模式的痛点:

// 反例:把操作逻辑耦合在数据结构中
type FileData struct {
    Path string
}

func (f *FileData) Process() {
    // 文件处理逻辑
}

func (f *FileData) Validate() {
    // 文件验证逻辑
}

func (f *FileData) Encrypt() {
    // 文件加密逻辑
}
// 每新增一种操作,都要修改FileData结构!

问题:

  • 新增操作需要修改数据结构,违反开闭原则
  • 数据结构越来越臃肿
  • 不同操作混在一起,难以维护

Visitor模式的结构

┌─────────────────────────────────────┐
│         Visitor Interface           │
│  Visit(Element) error               │
└─────────────┬───────────────────────┘
              │
    ┌─────────┼─────────┐
    ↓         ↓         ↓
┌───────┐ ┌───────┐ ┌───────┐
│FileVisitor│ │URLVisitor│ │StdinVisitor│
└───────┘ └───────┘ └───────┘
              │
              ↓
┌─────────────────────────────────────┐
│         Element (Data)              │
└─────────────────────────────────────┘

核心组件:

角色职责kubectl中的对应
Visitor声明访问接口Visitor接口
ConcreteVisitor实现具体访问逻辑FileVisitor, StreamVisitor
Element被访问的数据元素Info结构体
VisitorFunc对元素的操作函数VisitorFunc回调

kubectl的Visitor模式实战

kubectl需要处理来自不同数据源的资源(本地文件、HTTP、stdin、Kustomize等),这正是Visitor模式的典型应用场景。

Visitor接口定义

pkg/resource/interfaces.go中,kubectl定义了简洁的Visitor接口:

// Visitor lets clients walk a list of resources.
// Visitor让客户端能够遍历资源列表
type Visitor interface {
    Visit(VisitorFunc) error
}

// VisitorFunc implements the Visitor interface for a matching function.
// 对单个资源元素的处理函数
// 如果遍历过程中出错,传入的error会描述问题,函数可以决定如何处理
// 返回nil表示接受错误并继续遍历,返回error则停止遍历
type VisitorFunc func(*Info, error) error

// Info contains temporary info to execute a REST call
// Info包含了执行REST调用所需的临时信息
type Info struct {
    Source          string          // 数据来源(文件名/URL)
    Namespace       string          // 命名空间
    Name            string          // 资源名称
    ResourceVersion string          // 资源版本(用于乐观锁)
    Object          runtime.Object  // 解码后的K8s对象
    Mapping         *meta.RESTMapping // GVK到GVR的映射
    Client          RESTClient      // REST客户端
    // ... 其他字段
}

设计要点:

  • Visitor只定义一个方法Visit,接收一个处理函数
  • VisitorFunc是实际的业务逻辑,操作*Info对象
  • Info是统一的数据结构,屏蔽了底层数据源差异

各种Visitor实现

kubectl实现了多种Visitor来处理不同的数据源:

1. FileVisitor:文件数据源
// FileVisitor处理本地文件
type FileVisitor struct {
    Path          string         // 文件路径
    *StreamVisitor              // 嵌入StreamVisitor处理文件内容
}

func (v *FileVisitor) Visit(fn VisitorFunc) error {
    var f *os.File
    
    // 支持stdin(路径为"-")
    if v.Path == constSTDINstr {
        f = os.Stdin
    } else {
        // 打开本地文件
        var err error
        f, err = os.Open(v.Path)
        if err != nil {
            return err
        }
        defer f.Close()
    }
    
    // 处理UTF-16 BOM(Windows兼容性)
    utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder())
    v.StreamVisitor.Reader = transform.NewReader(f, utf16bom)
    
    // 委托给StreamVisitor处理内容
    return v.StreamVisitor.Visit(fn)
}

特点:

  • 负责文件的打开和关闭(资源管理)
  • 处理编码问题(UTF-16 BOM)
  • 实际内容解析委托给StreamVisitor
2. URLVisitor:HTTP数据源
// URLVisitor处理HTTP/HTTPS URL
type URLVisitor struct {
    URL        *url.URL
    *StreamVisitor
    HTTPAttemptCount int // 重试次数
}

func (v *URLVisitor) Visit(fn VisitorFunc) error {
    // HTTP GET请求获取内容
    body, err := readHttpWithRetries(
        v.HTTPAttemptCount, 
        "GET", 
        v.URL.String(),
    )
    if err != nil {
        return err
    }
    defer body.Close()
    
    // 设置Reader并委托给StreamVisitor
    v.StreamVisitor.Reader = body
    return v.StreamVisitor.Visit(fn)
}

特点:

  • 支持HTTP/HTTPS协议
  • 内置重试机制
  • 统一的StreamVisitor处理流式数据
3. StreamVisitor:统一的内容处理
// StreamVisitor处理任意io.Reader
type StreamVisitor struct {
    Reader          io.Reader
    Mapper          *mapper
    Source          string
    Schema          ContentValidator
}

func (v *StreamVisitor) Visit(fn VisitorFunc) error {
    // 使用YAML/JSON解码器
    d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096)
    
    // 循环解码多个资源对象(一个文件可能有多个YAML文档)
    for {
        ext := runtime.RawExtension{}
        
        // 解码一个对象
        if err := d.Decode(&ext); err != nil {
            if err == io.EOF {
                return nil  // 正常结束
            }
            return fmt.Errorf("error parsing %s: %v", v.Source, err)
        }
        
        // 跳过空文档
        ext.Raw = bytes.TrimSpace(ext.Raw)
        if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
            continue
        }
        
        // Schema验证(可选)
        if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
            return fmt.Errorf("error validating %q: %v", v.Source, err)
        }
        
        // 转换为Info对象
        info, err := v.infoForData(ext.Raw, v.Source)
        if err != nil {
            // 调用VisitorFunc处理错误(可以选择继续或中断)
            if fnErr := fn(info, err); fnErr != nil {
                return fnErr
            }
            continue
        }
        
        // 调用VisitorFunc处理正常数据
        if err := fn(info, nil); err != nil {
            return err
        }
    }
}

关键点:

  • 一个文件可能包含多个YAML文档(用---分隔),所以需要循环解码
  • 支持YAML和JSON格式自动识别
  • 错误处理灵活:可以通过VisitorFunc决定遇到错误是继续还是中断

Info对象:统一的数据表示

StreamVisitor通过infoForData将原始数据转换为统一的Info对象:

func (m *mapper) infoForData(data []byte, source string) (*Info, error) {
    // 1. 解码YAML/JSON为runtime.Object
    obj, gvk, err := m.decoder.Decode(data, nil, nil)
    if err != nil {
        return nil, fmt.Errorf("unable to decode %q: %v", source, err)
    }
    
    // 2. 提取元数据
    name, _ := metadataAccessor.Name(obj)
    namespace, _ := metadataAccessor.Namespace(obj)
    resourceVersion, _ := metadataAccessor.ResourceVersion(obj)
    
    // 3. 构建Info对象
    info := &Info{
        Source:          source,
        Namespace:       namespace,
        Name:            name,
        ResourceVersion: resourceVersion,
        Object:          obj,
    }
    
    // 4. 如果不是本地模式,获取RESTMapping和Client
    if m.localFn == nil || !m.localFn() {
        restMapper, err := m.restMapperFn()
        if err != nil {
            return nil, err
        }
        
        // GVK -> GVR的映射(如Pod -> pods)
        mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
        if err != nil {
            return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
        }
        info.Mapping = mapping
        
        // 创建REST客户端
        client, err := m.clientFn(gvk.GroupVersion())
        if err != nil {
            return nil, fmt.Errorf("unable to connect to a server: %v", err)
        }
        info.Client = client
    }
    
    return info, nil
}

Info对象的设计意义:

  • 屏蔽数据源差异:无论来自文件、URL还是stdin,最终都是*Info
  • 包含完整上下文:从原始数据到API调用的所有信息
  • 延迟加载:Client和Mapping按需创建

DecoratedVisitor:装饰器模式增强

kubectl还使用了装饰器模式来增强Visitor功能,这是Visitor模式的高级应用。

场景:给Visitor添加通用处理

在真正执行业务逻辑前,kubectl需要对每个资源执行一些通用操作:

  • 设置默认namespace
  • 校验namespace一致性
  • 确保对象非空

如果用继承或修改原有Visitor,会违反开闭原则。kubectl使用装饰器模式优雅解决这个问题:

// DecoratedVisitor在原有Visitor基础上添加装饰函数
type DecoratedVisitor struct {
    visitor    Visitor           // 被装饰的Visitor
    decorators []VisitorFunc     // 装饰函数列表
}

func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
    return &DecoratedVisitor{visitor: v, decorators: fn}
}

func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
    return v.visitor.Visit(func(info *Info, err error) error {
        if err != nil {
            return err
        }
        
        // 先执行所有装饰函数
        for i := range v.decorators {
            if err := v.decorators[i](info, nil); err != nil {
                return err
            }
        }
        
        // 再执行真正的业务逻辑
        return fn(info, nil)
    })
}

装饰函数示例

// SetNamespace 设置默认namespace
func SetNamespace(namespace string) VisitorFunc {
    return func(info *Info, err error) error {
        if err != nil {
            return err
        }
        if info.Namespaced() && len(info.Namespace) == 0 {
            info.Namespace = namespace
            UpdateObjectNamespace(info, nil)
        }
        return nil
    }
}

// RequireNamespace 要求必须匹配指定namespace
func RequireNamespace(namespace string) VisitorFunc {
    return func(info *Info, err error) error {
        if err != nil {
            return err
        }
        if !info.Namespaced() {
            return nil
        }
        if len(info.Namespace) == 0 {
            info.Namespace = namespace
            return nil
        }
        if info.Namespace != namespace {
            return fmt.Errorf(
                "the namespace from the provided object %q does not match the namespace %q",
                info.Namespace, namespace,
            )
        }
        return nil
    }
}

// FilterNamespace 过滤namespace相关信息
func FilterNamespace(info *Info, err error) error {
    if err != nil {
        return err
    }
    // 清理内部namespace标记
    if info.Object != nil {
        acc, _ := meta.Accessor(info.Object)
        if acc != nil {
            acc.SetNamespace("")
        }
    }
    return nil
}

// RetrieveLazy 确保对象已加载
func RetrieveLazy(info *Info, err error) error {
    if err != nil {
        return err
    }
    if info.Object != nil {
        return nil
    }
    // 延迟加载对象
    return info.Get()
}

组合使用装饰器

// 在Builder.Do()中组合装饰器
func (b *Builder) Do() *Result {
    r := b.visitorResult()
    
    // 收集装饰函数
    helpers := []VisitorFunc{}
    
    if b.defaultNamespace {
        helpers = append(helpers, SetNamespace(b.namespace))
    }
    if b.requireNamespace {
        helpers = append(helpers, RequireNamespace(b.namespace))
    }
    helpers = append(helpers, FilterNamespace)
    if b.requireObject {
        helpers = append(helpers, RetrieveLazy)
    }
    
    // 创建装饰后的Visitor
    if b.continueOnError {
        // ContinueOnErrorVisitor是另一个装饰器
        r.visitor = NewDecoratedVisitor(
            ContinueOnErrorVisitor{r.visitor}, 
            helpers...,
        )
    } else {
        r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
    }
    
    return r
}

装饰器模式的优势:

  • 开闭原则:新增处理逻辑只需添加装饰函数,不改原有代码
  • 灵活组合:根据需要组合不同的装饰函数
  • 单一职责:每个装饰函数只做一件事

ContinueOnErrorVisitor:错误处理装饰器

// ContinueOnErrorVisitor收集所有错误,最后一起返回
type ContinueOnErrorVisitor struct {
    Visitor
}

func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
    errs := []error{}
    
    err := v.Visitor.Visit(func(info *Info, err error) error {
        if err != nil {
            errs = append(errs, err)
            return nil  // 继续遍历
        }
        if err := fn(info, nil); err != nil {
            errs = append(errs, err)
            return nil  // 继续遍历
        }
        return nil
    })
    
    if err != nil {
        errs = append(errs, err)
    }
    
    // 返回所有错误的聚合
    return utilerrors.NewAggregate(errs)
}

使用场景: 批量处理资源时,一个失败不应该影响其他资源的处理。

kubectl create中的完整调用链

让我们完整走一遍kubectl create的Visitor调用链:

用户执行:kubectl create -f app.yaml

1. FilenameParam解析-f参数
   └─ 创建FileVisitor(如果是文件)
      └─ 嵌入StreamVisitor

2. Builder.Do()构建Visitor链
   └─ 创建DecoratedVisitor
      ├─ 包装:FlattenListVisitor(拍平列表)
      └─ 添加装饰函数:
          ├─ SetNamespace(设置默认namespace)
          ├─ RequireNamespace(校验namespace)
          └─ FilterNamespace(过滤namespace)

3. Result.Visit()开始遍历
   └─ DecoratedVisitor.Visit()
      ├─ 执行装饰函数
      └─ FileVisitor.Visit()
         ├─ 打开文件
         └─ StreamVisitor.Visit()
            ├─ YAML/JSON解码
            ├─ Schema验证
            ├─ 创建Info对象
            └─ 调用业务VisitorFunc
               ├─ CreateOrUpdateAnnotation
               ├─ Record
               └─ Create(发送HTTP请求)

业务VisitorFunc:真正干活的地方

kubectl create中,业务逻辑通过VisitorFunc注入:

err = r.Visit(func(info *resource.Info, err error) error {
    // 1. 错误处理
    if err != nil {
        return err
    }
    
    // 2. 添加kubectl管理的注解(用于apply识别)
    if err := util.CreateOrUpdateAnnotation(
        info.Object, 
        scheme.DefaultJSONEncoder(),
    ); err != nil {
        return cmdutil.AddSourceToErr("creating", info.Source, err)
    }
    
    // 3. 录制变更历史(审计)
    if err := o.Recorder.Record(info.Object); err != nil {
        klog.V(4).Infof("error recording: %v", err)
    }
    
    // 4. 执行真正的创建(非dry-run模式)
    if o.DryRunStrategy != cmdutil.DryRunClient {
        if o.DryRunStrategy == cmdutil.DryRunServer {
            // 检查server是否支持dry-run
            if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
                return cmdutil.AddSourceToErr("creating", info.Source, err)
            }
        }
        
        // 发送HTTP POST创建资源
        obj, err := resource.
            NewHelper(info.Client, info.Mapping).
            DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
            WithFieldManager(o.fieldManager).
            Create(info.Namespace, true, info.Object)
        
        if err != nil {
            return cmdutil.AddSourceToErr("creating", info.Source, err)
        }
        
        // 更新Info对象
        info.Refresh(obj, true)
    }
    
    // 5. 打印结果
    return o.PrintObj(info.Object, o.IOStreams.Out)
})

VisitorFunc的职责:

  • 操作单个资源(*Info
  • 决定遇到错误时的处理方式(返回error中断,或返回nil继续)
  • 可以嵌套调用其他VisitorFunc(装饰器模式)

给你的项目设计Visitor模式的建议

如果你的项目需要处理异构数据集合,可以参考kubectl的设计:

标准实现模板

package main

import (
    "fmt"
    "io"
)

// ========== 1. 定义Visitor接口 ==========

type DataInfo struct {
    Source  string
    Content []byte
    Type    string
    // ... 其他元数据
}

type VisitorFunc func(*DataInfo, error) error

type Visitor interface {
    Visit(VisitorFunc) error
}

// ========== 2. 实现具体Visitor ==========

// FileVisitor 处理文件
type FileVisitor struct {
    Path string
}

func (v *FileVisitor) Visit(fn VisitorFunc) error {
    content, err := os.ReadFile(v.Path)
    if err != nil {
        return fn(nil, err)
    }
    
    return fn(&DataInfo{
        Source:  v.Path,
        Content: content,
        Type:    "file",
    }, nil)
}

// HTTPVisitor 处理HTTP
type HTTPVisitor struct {
    URL string
}

func (v *HTTPVisitor) Visit(fn VisitorFunc) error {
    resp, err := http.Get(v.URL)
    if err != nil {
        return fn(nil, err)
    }
    defer resp.Body.Close()
    
    content, err := io.ReadAll(resp.Body)
    if err != nil {
        return fn(nil, err)
    }
    
    return fn(&DataInfo{
        Source:  v.URL,
        Content: content,
        Type:    "http",
    }, nil)
}

// MultiVisitor 组合多个Visitor
type MultiVisitor []Visitor

func (v MultiVisitor) Visit(fn VisitorFunc) error {
    for _, visitor := range v {
        if err := visitor.Visit(fn); err != nil {
            return err
        }
    }
    return nil
}

// ========== 3. 装饰器增强 ==========

// ValidatedVisitor 添加验证功能
type ValidatedVisitor struct {
    Visitor
    Validator func([]byte) error
}

func (v *ValidatedVisitor) Visit(fn VisitorFunc) error {
    return v.Visitor.Visit(func(info *DataInfo, err error) error {
        if err != nil {
            return fn(info, err)
        }
        if err := v.Validator(info.Content); err != nil {
            return fn(info, fmt.Errorf("validation failed: %v", err))
        }
        return fn(info, nil)
    })
}

// ContinueOnErrorVisitor 收集所有错误
type ContinueOnErrorVisitor struct {
    Visitor
}

func (v *ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
    var errs []error
    
    v.Visitor.Visit(func(info *DataInfo, err error) error {
        if err != nil {
            errs = append(errs, err)
            return nil  // 继续
        }
        if err := fn(info, nil); err != nil {
            errs = append(errs, err)
        }
        return nil
    })
    
    if len(errs) > 0 {
        return fmt.Errorf("encountered %d errors: %v", len(errs), errs)
    }
    return nil
}

// ========== 4. 使用示例 ==========

func main() {
    // 创建多个Visitor
    visitors := MultiVisitor{
        &FileVisitor{Path: "config1.yaml"},
        &FileVisitor{Path: "config2.yaml"},
        &HTTPVisitor{URL: "http://example.com/config.yaml"},
    }
    
    // 添加验证装饰器
    validatedVisitor := &ValidatedVisitor{
        Visitor: visitors,
        Validator: func(data []byte) error {
            // 简单的YAML格式校验
            if !bytes.HasPrefix(data, []byte("---")) {
                return fmt.Errorf("not a valid YAML")
            }
            return nil
        },
    }
    
    // 添加错误处理装饰器
    continueVisitor := &ContinueOnErrorVisitor{Visitor: validatedVisitor}
    
    // 执行遍历
    err := continueVisitor.Visit(func(info *DataInfo, err error) error {
        if err != nil {
            fmt.Printf("Error processing %s: %v\n", info.Source, err)
            return nil
        }
        
        fmt.Printf("Processing %s (%d bytes)\n", info.Source, len(info.Content))
        // 处理数据...
        return nil
    })
    
    if err != nil {
        fmt.Printf("Final error: %v\n", err)
    }
}

Visitor模式 vs 其他模式

模式适用场景与Visitor的区别
Iterator遍历集合Iterator关注"如何遍历",Visitor关注"遍历后做什么"
Strategy算法替换Strategy替换整个算法,Visitor替换对元素的操作
Command请求封装Command封装请求,Visitor封装对数据的操作
Chain of Responsibility责任链责任链是线性传递,Visitor是遍历+操作分离

踩坑实录:Visitor模式的坑

坑1:错误处理不当

现象:Visitor遇到错误就返回,后面的数据没有机会处理。

// 错误的实现
func (v *BadVisitor) Visit(fn VisitorFunc) error {
    for _, item := range v.items {
        info := convert(item)
        if err := doSomething(info); err != nil {
            return err  // 直接返回,中断遍历
        }
        if err := fn(info, nil); err != nil {
            return err  // 直接返回,中断遍历
        }
    }
    return nil
}

解决方案:提供ContinueOnError机制,把错误处理权交给VisitorFunc。

func (v *GoodVisitor) Visit(fn VisitorFunc) error {
    var errs []error
    
    for _, item := range v.items {
        info, err := convert(item)
        if err != nil {
            // 交给VisitorFunc决定
            if fnErr := fn(nil, err); fnErr != nil {
                return fnErr  // VisitorFunc要求中断
            }
            continue  // VisitorFunc要求继续
        }
        
        if err := fn(info, nil); err != nil {
            errs = append(errs, err)
            // 或者根据策略决定是否继续
        }
    }
    
    return aggregateErrors(errs)
}

坑2:资源泄漏

现象:FileVisitor打开文件后忘记关闭。

// 错误的实现
func (v *FileVisitor) Visit(fn VisitorFunc) error {
    f, err := os.Open(v.Path)
    if err != nil {
        return err
    }
    // 哎呀,忘记defer f.Close()了!
    
    return fn(&DataInfo{...}, nil)
}

解决方案:使用defer确保资源释放。

func (v *FileVisitor) Visit(fn VisitorFunc) error {
    f, err := os.Open(v.Path)
    if err != nil {
        return err
    }
    defer f.Close()  // 确保关闭
    
    return fn(&DataInfo{...}, nil)
}

坑3:VisitorFunc递归调用死循环

现象:VisitorFunc中调用了会再次触发Visit的方法,导致死循环。

err := visitor.Visit(func(info *Info, err error) error {
    // 错误:再次调用Visit!
    return visitor.Visit(func(info2 *Info, err error) error {
        // ...
    })
})

解决方案:明确Visitor和VisitorFunc的职责边界,VisitorFunc不应该再调用Visitor。

坑4:并发安全问题

现象:多个goroutine共享同一个Visitor,导致数据竞争。

visitor := &FileVisitor{Path: "config.yaml"}

// 并发使用
for i := 0; i < 10; i++ {
    go func() {
        visitor.Visit(fn)  // 竞态条件!
    }()
}

解决方案:Visitor通常不是线程安全的,每个goroutine应该有自己的Visitor实例,或者加锁保护。

坑5:装饰器顺序问题

现象:装饰器顺序不当导致逻辑错误。

// 错误的顺序:先加密再验证,验证会失败
visitor := &ValidatedVisitor{
    Visitor: &EncryptedVisitor{...},  // 先加密
    Validator: validatePlainText,      // 再验证明文
}

解决方案:装饰器顺序很重要,要根据业务逻辑合理安排。

Visitor模式的优缺点总结

优点

  1. 开闭原则:新增操作只需添加VisitorFunc,不改数据结构
  2. 单一职责:数据结构只管存储,操作逻辑分离
  3. 灵活组合:通过装饰器灵活组合各种操作
  4. 统一接口:不同类型的数据源用统一方式处理

缺点

  1. 元素类型受限:新增元素类型需要修改Visitor接口
  2. 破坏封装:Visitor需要访问元素的内部状态
  3. 理解成本:相比直接的if-else,Visitor模式更抽象
  4. 过度设计:简单场景使用会增加复杂度

什么时候不该用Visitor模式?

场景建议方案原因
数据类型单一直接处理没有多态需求
操作类型固定方法调用不需要扩展
简单脚本顺序执行过度设计
性能敏感内联处理减少抽象层开销

总结

从kubectl的源码,我们学到了Visitor模式的精髓:

  1. 核心思想:数据结构与操作分离,通过Visitor统一遍历
  2. kubectl实现:FileVisitor、StreamVisitor处理不同数据源,统一输出*Info
  3. 装饰器增强:DecoratedVisitor灵活添加通用处理逻辑
  4. 错误处理:通过VisitorFunc返回error控制遍历流程
  5. 实战技巧:资源管理、错误累积、装饰器顺序等

Visitor模式特别适合异构数据集合+多种处理操作的场景。如果你的项目有类似需求,不妨参考kubectl的设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加倍巴巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值