UP | HOME

访问者

Table of Contents

Visitor 是面向对象设计模英中一个很重要的设计模款,这个模式是一种将 算法操作对象的结构 分离的一种方法。这种分离的实际结果是能够 在不修改结构的情况下向现有对象结构添加新操作 ,是遵循 开放/封闭 原则的一种方法

这篇文章重点看一下 kubelet 中是怎么使用函数式的方法来实现这个模式的

简单示例

先来看一个简单设计模式的Visitor的示例:

  • 有一个 Visitor的函数 定义,还有一个 Shape接口 ,其需要使用 Visitor函数做为参数
  • 实例对象 CircleRectangle 实现了 Shape 的接口的 accept() 方法,这个方法就是等外面给它传递一个Visitor
package main

import (
        "encoding/json"
        "encoding/xml"
        "fmt"
)

type Visitor func(shape Shape)

type Shape interface {
        accept(Visitor)
}

type Circle struct {
        Radius int
}

func (c Circle) accept(v Visitor) {
        v(c)
}

type Rectangle struct {
        Width, Heigh int
}

func (r Rectangle) accept(v Visitor) {
        v(r)
}

接下来实现两个Visitor,一个是用来做JSON序列化的,另一个是用来做XML序列化的:

func JsonVisitor(shape Shape) {
        bytes, err := json.Marshal(shape)
        if err != nil {
                panic(err)
        }
        fmt.Println(string(bytes))
}

func XmlVisitor(shape Shape) {
        bytes, err := xml.Marshal(shape)
        if err != nil {
                panic(err)
        }
        fmt.Println(string(bytes))
}

使用Visitor这个模式的代码:

func main() {
        c := Circle{10}
        r :=  Rectangle{100, 200}
        shapes := []Shape{c, r}

        for _, s := range shapes {
                s.accept(JsonVisitor)
                s.accept(XmlVisitor)
        }

}

其实,这段代码的目的就是想解耦 数据结构算法

使用 Strategy 模式也是可以完成的,而且会比较干净

但是有些情况下,多个Visitor是来访问 同一个数据结构的不同部分

这种情况下,数据结构有点像一个数据库,而各个Visitor会成为一个个小应用

kubectl就是这种情况

k8s相关背景

来了解一下相关的知识背景:

  • 对于Kubernetes,其抽象了很多种的Resource,比如:Pod, ReplicaSet, ConfigMap, Volumes, Namespace, Roles …
    • 种类非常繁多,这些东西构成为了Kubernetes的数据模型
  • kubectl 是Kubernetes中的一个客户端命令,操作人员用这个命令来操作Kubernetes
    • kubectl 会联系到 Kubernetes 的API Server,API Server会联系每个节点上的 kubelet ,从而达到控制每个结点
    • kubectl 主要的工作是处理用户提交的东西(包括,命令行参数,yaml文件等),然后其会把用户提交的这些东西组织成一个数据结构体,然后把其发送给 API Server。
kubectl 的代码比较复杂,不过,其本原理简单来说:

它从命令行和yaml文件中获取信息,通过Builder模式并把其转成一系列的资源

最后用 Visitor 模式模式来迭代处理这些Reources

下面来看看 kubectl 的实现,为了简化,用一个小的示例来表明 ,而不是直接分析复杂的源码

kubectl的实现方法

Visitor模式定义

首先,kubectl 主要是用来处理 Info结构体 ,下面是相关的定义:

type VisitorFunc func(*Info, error) error

type Visitor interface {
        Visit(VisitorFunc) error
}

type Info struct {
        Namespace   string
        Name        string
        OtherThings string
}

func (info *Info) Visit(fn VisitorFunc) error {
        return fn(info, nil)
}
  • 一个 VisitorFunc函数类型 的定义
  • 一个 Visitor 的接口 ,其中需要 Visit(VisitorFunc) error 的方法
  • Info 实现 Visitor 接口 中的 Visit() 方法 ,实现就是 直接调用传进来的方法

再来定义几种不同类型的 Visitor

实现Visitor

Name Visitor

主要是用来 访问 Info 结构中的 NameNameSpace 成员:

type NameVisitor struct {
        visitor Visitor
}

func (v NameVisitor) Visit(fn VisitorFunc) error {
        return v.visitor.Visit(func(info *Info, err error) error {
                fmt.Println("NameVisitor() before call function")
                err = fn(info, err)
                if err == nil {
                        fmt.Printf("==> Name=%s, NameSpace=%s\n", info.Name, info.Namespace)
                }
                fmt.Println("NameVisitor() after call function")
                return err
        })
}
  • 声明了一个 NameVisitor 的结构体 ,这个结构体里有一个 Visitor 接口成员 ,这里意味着 多态
  • 实现 Visit() 方法时,其调用了自己结构体内的那个 Visitor的 Visitor() 方法
    • 这其实是一种修饰器的模式,用另一个Visitor修饰了自己
Other Visitor

这个Visitor主要用来访问 Info 结构中的 OtherThings 成员

type OtherThingsVisitor struct {
        visitor Visitor
}

func (v OtherThingsVisitor) Visit(fn VisitorFunc) error {
        return v.visitor.Visit(func(info *Info, err error) error {
                fmt.Println("OtherThingsVisitor() before call function")
                err = fn(info, err)
                if err == nil {
                        fmt.Printf("==> OtherThings=%s\n", info.OtherThings)
                }
                fmt.Println("OtherThingsVisitor() after call function")
                return err
        })
}
Log Visitor
type LogVisitor struct {
        visitor Visitor
}

func (v LogVisitor) Visit(fn VisitorFunc) error {
        return v.visitor.Visit(func(info *Info, err error) error {
                fmt.Println("LogVisitor() before call function")
                err = fn(info, err)
                fmt.Println("LogVisitor() after call function")
                return err
        })
}

调用Vistor的代码

如果使用上面的代码:

func main() {
        info := Info{}
        var v Visitor = &info
        v = LogVisitor{v}
        v = NameVisitor{v}
        v = OtherThingsVisitor{v}

        loadFile := func(info *Info, err error) error {
                info.Name = "Hao Chen"
                info.Namespace = "MegaEase"
                info.OtherThings = "We are running as remote team."
                return nil
        }
        v.Visit(loadFile)
}
可以看到:
1. Visitor们一层套一层
2. 用 loadFile 假装从文件中读如数据
3. v.Visit(loadfile) 上面的代码就全部开始激活工作了 

上面的代码输出如下的信息

LogVisitor() before call function
NameVisitor() before call function
OtherThingsVisitor() before call function
==> OtherThings=We are running as remote team.
OtherThingsVisitor() after call function
==> Name=Hao Chen, NameSpace=MegaEase
NameVisitor() after call function
LogVisitor() after call function
请思考下代码的执行顺序是怎么样的

上面的代码有以下几种功效:

  • 解耦了数据和程序
  • 使用了修饰器模式
  • 做出来pipeline的模式

Previous:管道

Home:目录