概述

最近在看kubernetes的kubectl部分源码,记录一下其中用到的visitor编程模式(实际上kubectl主要用到了builder和visitor)。visitor模式是将算法和操作对象结构分离的一种方法。换句话说,这样的分离能够在不修改对象结构的情况下向原有对象新增操作,是符合开闭原则的。这个文章以一些例子去讨论kubectl中到底如何玩的。

从一个例子出发

写一个简单的Visitor模式示例:

  • 我们的代码中有一个Visitor的函数定义,还有一个Shape接口,其需要使用 Visitor函数做为参数
  • 我们的实例的对象 CircleRectangle实现了 Shape 的接口的 accept() 方法,这个方法就是等外面给我传递一个Visitor。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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序列化的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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这个模式的代码:

1
2
3
4
5
6
7
8
9
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 & docker的专栏里,介绍了k8s的一些基本知识。其实对于k8s来说,其抽象出了很多资源Resource:Pod,ReplicaSet,ConfigMap,Volumes,Namespace, Roles…等等。而这些构成了k8s的数据模型( Kubernetes Resources 地图)

  • kubectl为k8s的客户端命令,其对接Kubernetes API Server,开发和运维通过此去和k8s进行交互。而API Server则联系到每个节点的kubelet控制每个节点。

  • kubectl主要的工作就是处理用户提交的例如:命令行参数、yaml/json文件等。将用户提交的这些组织成数据结构体,发送给API Server。

  • 源码:src/k8s.io/cli-runtime/pkg/resource/visitor.go (链接)

当然kubectl的源码复杂,用简单的话阐述其基本原理就是:它从命令行和yaml文件中获取信息,通过Builder模式并把其转成一系列的资源,最后用 Visitor 模式模式来迭代处理这些Reources

我先用一个小的例子来说明,忽略掉很多复杂的代码逻辑

kubectl的实现

Visitor模式的定义

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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()方法,其就是直接调用传进来的fn

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

Name Visitor

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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
  })
}

拆解代码,可以看到:

  • 声明了一个NameVistor结构体,多态得加了一个Visitor接口成员
  • 实现Visit()方法时,调用内部VisitorVisit()方法,这也是一种修饰器模式。

Other Visitor

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
  })
}

如何使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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)
}

拆解上述代码:

  • Visitor为嵌套式的
  • LoadFile模拟读取文件数据
  • 最后一条v.Visit()激活上述流程

上述的代码输出如下:

1
2
3
4
5
6
7
8
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

我们可以看到,这种做法实现了几点功能:

  1. 解耦了数据和算法程序
  2. 使用修饰器模式
  3. 有pipeline模式的味道

我们接下来再以修饰器模式重构下上述代码

Visitor修饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type DecoratedVisitor struct {
  visitor    Visitor
  decorators []VisitorFunc
}
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
  if len(fn) == 0 {
    return v
  }
  return DecoratedVisitor{v, fn}
}
// Visit implements Visitor
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
  return v.visitor.Visit(func(info *Info, err error) error {
    if err != nil {
      return err
    }
    if err := fn(info, nil); err != nil {
      return err
    }
    for i := range v.decorators {
      if err := v.decorators[i](info, nil); err != nil {
        return err
      }
    }
    return nil
  })
}

上述代码,实际上做了以下几点事情:

  • DecoratedVisitor结构存放所有的VisitorFunc
  • NewDecoratedVisitor把所有的VisitorFunc传进去,构造DecoratedVisitor对象
  • DecoratedVisitor实现了Visit()方法,里面实际上就是个for-loop,以非嵌套的方式调用所有的VisitorFunc

所以我们可以这么使用这个重构:

1
2
3
4
info := Info{}
var v Visitor = &info
v = NewDecoratedVisitor(v, NameVisitor, OtherVisitor)
v.Visit(LoadFile)

这样看上去能简单很多。

基本上如果读懂了上述的逻辑,kubectl的代码也差不多能看明白。