你好,我是陈皓,网名左耳朵耗子。

这节课,我们来重点讨论一下,Kubernetes 的 kubectl 命令中的使用到的一个编程模式:Visitor(其实,kubectl 主要使用到了两个,一个是Builder,另一个是Visitor)。

本来,Visitor 是面向对象设计模式中一个很重要的设计模式(可以看下Wikipedia Visitor Pattern词条),这个模式是将算法与操作对象的结构分离的一种方法。这种分离的实际结果是能够在不修改结构的情况下向现有对象结构添加新操作,是遵循开放/封闭原则的一种方法。这节课,我们重点学习一下 kubelet 中是怎么使用函数式的方法来实现这个模式的。

一个简单示例

首先,我们来看一个简单设计模式的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就是这种情况。

Kubernetes相关背景

接下来,我们再来了解一下相关的知识背景。

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)
}

可以看到,

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

Name Visitor

这个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
  })
}

可以看到,在这段代码中:

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
  })
}

使用方代码

现在,我们看看使用上面的代码:

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)
}

可以看到,

这段代码输出如下的信息,你可以看到代码是怎么执行起来的:

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

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

所以,其实我们可以重构一下上面的代码。

Visitor修饰器

我们用修饰器模式来重构一下上面的代码。

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
  })
}

这段代码并不复杂,我来解释下。

这样,我们的代码就可以这样运作了:

info := Info{}
var v Visitor = &info
v = NewDecoratedVisitor(v, NameVisitor, OtherVisitor)

v.Visit(LoadFile)

是不是比之前的那个简单?需要注意的是,这个DecoratedVisitor 同样可以成为一个Visitor来使用。上面的这些代码全部存在于 kubectl 的代码中,只要你看懂了这里面的代码逻辑,就一定能看懂 kubectl 的代码。

好了,这节课就到这里。如果你觉得今天的内容对你有所帮助,欢迎你帮我分享给更多人。