golang設計模式-以kubernetes源碼爲例

概述

design pattern 介紹設計模式
engineer pattern 總結先進的工程模式node

design pattern

參考git

simplefactory

對golang來講就是Newxx函數,返回interface, kubernetes interface隨處可見,能夠說能用interface抽象的就是interface,隨便舉一個例子github

// k8s.io/kubernetes/vendor/k8s.io/client-go/tools/cache/store.go
func NewStore(keyFunc KeyFunc) Store {
    return &cache{
        cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
        keyFunc:      keyFunc,
    }
}

type cache struct {
    // cacheStorage bears the burden of thread safety for the cache
    cacheStorage ThreadSafeStore
    // keyFunc is used to make the key for objects stored in and retrieved from items, and
    // should be deterministic.
    keyFunc KeyFunc
}

type Store interface {
    Add(obj interface{}) error
    Update(obj interface{}) error
    Delete(obj interface{}) error
    List() []interface{}
    ListKeys() []string
    Get(obj interface{}) (item interface{}, exists bool, err error)
    GetByKey(key string) (item interface{}, exists bool, err error)

    // Replace will delete the contents of the store, using instead the
    // given list. Store takes ownership of the list, you should not reference
    // it after calling this function.
    Replace([]interface{}, string) error
    Resync() error
}複製代碼

facade / adapter / decorator / delegate / bridge / mediator / composite

組合模式的不一樣形式,這些隨處可見,也不用深究其中的區別。golang

singleton

kubernetes/golang 用得不多,通常使用全局變量(如配置) (好比 net/http package 的 http.DefaultClient 和 http.DefaultServeMux). 或者做爲context傳遞。
實現方式能夠參考marcio.io/2015/07/sin…算法

比較常見的一種方式是double checkjson

func GetInstance() *singleton {
    if instance == nil {     // <-- Not yet perfect. since it's not fully atomic mu.Lock() defer mu.Unlock() if instance == nil { instance = &singleton{} } } return instance }複製代碼

可是在golang裏面有更好的一種方式,用"Once"設計模式

type singleton struct {
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}複製代碼

factory/ abstract factory / builder

關於這幾種creational patterns 的區別:api

  • Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.
  • Builder often builds a Composite.
  • Often, designs start out using Factory Method (less complicated, more customizable, subclasses proliferate) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, more complex) as the designer discovers where more flexibility is needed.
  • Sometimes creational patterns are complementary: Builder can use one of the other patterns to implement which components get built. Abstract Factory, Builder, and Prototype can use Singleton in their implementations.

factorybash

// k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go
func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
    serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory)
    return newCodecFactory(scheme, serializers)
}

func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec {
    return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner)
}複製代碼

abstract factory 以SharedInformerFactory爲例,這個factory 可以create app/core/batch ...等各類interface數據結構

//k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/factory.go
func NewSharedInformerFactory(client internalclientset.Interface, defaultResync time.Duration) SharedInformerFactory {
    return &sharedInformerFactory{
        client:           client,
        defaultResync:    defaultResync,
        informers:        make(map[reflect.Type]cache.SharedIndexInformer),
        startedInformers: make(map[reflect.Type]bool),
    }
}

// SharedInformerFactory provides shared informers for resources in all known
// API group versions.
type SharedInformerFactory interface {
    internalinterfaces.SharedInformerFactory
    ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
    WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool

    Admissionregistration() admissionregistration.Interface
    Apps() apps.Interface
    Autoscaling() autoscaling.Interface
    Batch() batch.Interface
    Certificates() certificates.Interface
    Core() core.Interface
    Extensions() extensions.Interface
    Networking() networking.Interface
    Policy() policy.Interface
    Rbac() rbac.Interface
    Scheduling() scheduling.Interface
    Settings() settings.Interface
    Storage() storage.Interface
}

// sharedInformerFactory 是具體的stuct
func (f *sharedInformerFactory) Apps() apps.Interface {
    return apps.New(f)
}複製代碼

builder

// k8s.io/kubernetes/pkg/controller/client_builder.go

func NewForConfigOrDie(c *rest.Config) *Clientset {
    var cs Clientset
    cs.admissionregistrationV1alpha1 = admissionregistrationv1alpha1.NewForConfigOrDie(c)
    cs.appsV1beta1 = appsv1beta1.NewForConfigOrDie(c)
    cs.appsV1beta2 = appsv1beta2.NewForConfigOrDie(c)
    cs.appsV1 = appsv1.NewForConfigOrDie(c)
    cs.authenticationV1 = authenticationv1.NewForConfigOrDie(c)
    cs.authenticationV1beta1 = authenticationv1beta1.NewForConfigOrDie(c)
    cs.authorizationV1 = authorizationv1.NewForConfigOrDie(c)
    cs.authorizationV1beta1 = authorizationv1beta1.NewForConfigOrDie(c)
    cs.autoscalingV1 = autoscalingv1.NewForConfigOrDie(c)
    cs.autoscalingV2beta1 = autoscalingv2beta1.NewForConfigOrDie(c)
    cs.batchV1 = batchv1.NewForConfigOrDie(c)
    cs.batchV1beta1 = batchv1beta1.NewForConfigOrDie(c)
    cs.batchV2alpha1 = batchv2alpha1.NewForConfigOrDie(c)
    cs.certificatesV1beta1 = certificatesv1beta1.NewForConfigOrDie(c)
    cs.coreV1 = corev1.NewForConfigOrDie(c)
    cs.extensionsV1beta1 = extensionsv1beta1.NewForConfigOrDie(c)
    cs.networkingV1 = networkingv1.NewForConfigOrDie(c)
    cs.policyV1beta1 = policyv1beta1.NewForConfigOrDie(c)
    cs.rbacV1 = rbacv1.NewForConfigOrDie(c)
    cs.rbacV1beta1 = rbacv1beta1.NewForConfigOrDie(c)
    cs.rbacV1alpha1 = rbacv1alpha1.NewForConfigOrDie(c)
    cs.schedulingV1alpha1 = schedulingv1alpha1.NewForConfigOrDie(c)
    cs.settingsV1alpha1 = settingsv1alpha1.NewForConfigOrDie(c)
    cs.storageV1beta1 = storagev1beta1.NewForConfigOrDie(c)
    cs.storageV1 = storagev1.NewForConfigOrDie(c)

    cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
    return &cs
}複製代碼

prototype

原型模式是建立型模式的一種,其特色在於經過「複製」一個已經存在的實例來返回新的實例,而不是新建實例。被複制的實例就是咱們所稱的「原型」,這個原型是可定製的。
The Prototype Pattern creates duplicate objects while keeping performance in mind. It's a part of the creational patterns and provides one of the best ways to create an object.

參考blog.ralch.com/tutorial/de…

kubernetes使用了deepcopy-gen自動生成對象的deepcopy等方法,好比下面的一個生成的例子

// k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GroupResources) DeepCopyInto(out *GroupResources) {
    *out = *in
    if in.Resources != nil {
        in, out := &in.Resources, &out.Resources
        *out = make([]string, len(*in))
        copy(*out, *in)
    }
    if in.ResourceNames != nil {
        in, out := &in.ResourceNames, &out.ResourceNames
        *out = make([]string, len(*in))
        copy(*out, *in)
    }
    return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResources.
func (in *GroupResources) DeepCopy() *GroupResources {
    if in == nil {
        return nil
    }
    out := new(GroupResources)
    in.DeepCopyInto(out)
    return out
}複製代碼

生成的工具在這裏 k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/deepcopy-gen/main.go
使用 github.com/kubernetes/…

observer

這個模式在kubernetes裏面也比較常見,好比sharedInformer就是一個觀察者模式的實現

// k8s.io/client-go/tools/cache/shared_informer.go
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {
    ...
    s.processor.addListener(listener)
    ...
}


// 分發
func (p *sharedProcessor) distribute(obj interface{}, sync bool) {
    p.listenersLock.RLock()
    defer p.listenersLock.RUnlock()

    if sync {
        for _, listener := range p.syncingListeners {
            listener.add(obj)
        }
    } else {
        for _, listener := range p.listeners {
            listener.add(obj)
        }
    }
}複製代碼

command

定義: 命令模式(Command Pattern):將一個請求封裝爲一個對象,從而使咱們可用不一樣的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及支持可撤銷的操做。命令模式是一種對象行爲型模式,其別名爲動做(Action)模式或事務(Transaction)模式。
kubernetes 的command 基於 github.com/spf13/cobra, 把命令變成了對象,好比cmdRollOut還實現了undo。

interator

和數據結構相關的package裏面用得比較多.如引用的庫jsoniter, btree, govalidator.

// golang標準庫 "go/token" 實現了Iterate方法,可是其實這裏更像vistor模式...
func (s *FileSet) Iterate(f func(*File) bool)複製代碼

strategy

定義一系列算法,讓這些算法在運行時能夠互換,使得分離算法,符合開閉原則。對象有某個行爲,可是在不一樣的場景中,該行爲有不一樣的實現算法。
實際上使用interface的都像是strategy模式,從這方面看strategy模式只是字面上的強調意義,實現上和interface 實現的factory模式只是強調的點不同,一個是強調建立型,一個是強調行爲型
例子

// k8s每種對象都對應了一個strategy,決定了update,delete,get 的策略
// 好比 /k8s.io/kubernetes/pkg/registry/core/configmap/strategy.go

// strategy implements behavior for ConfigMap objects
type strategy struct {
    runtime.ObjectTyper
    names.NameGenerator
}

// Strategy is the default logic that applies when creating and updating ConfigMap
// objects via the REST API.
var Strategy = strategy{api.Scheme, names.SimpleNameGenerator}

// Strategy should implement rest.RESTCreateStrategy
var _ rest.RESTCreateStrategy = Strategy

// Strategy should implement rest.RESTUpdateStrategy
var _ rest.RESTUpdateStrategy = Strategy

func (strategy) NamespaceScoped() bool {
    return true
}

func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
    _ = obj.(*api.ConfigMap)
}

func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
    cfg := obj.(*api.ConfigMap)

    return validation.ValidateConfigMap(cfg)
}

// Canonicalize normalizes the object after validation.
func (strategy) Canonicalize(obj runtime.Object) {
}

func (strategy) AllowCreateOnUpdate() bool {
    return false
}

func (strategy) PrepareForUpdate(ctx genericapirequest.Context, newObj, oldObj runtime.Object) {
    _ = oldObj.(*api.ConfigMap)
    _ = newObj.(*api.ConfigMap)
}

func (strategy) AllowUnconditionalUpdate() bool {
    return true
}

func (strategy) ValidateUpdate(ctx genericapirequest.Context, newObj, oldObj runtime.Object) field.ErrorList {
    oldCfg, newCfg := oldObj.(*api.ConfigMap), newObj.(*api.ConfigMap)

    return validation.ValidateConfigMapUpdate(newCfg, oldCfg)
}



// k8s.io/kubernetes/pkg/registry/core/configmap/storage/storage.go
// NewREST returns a RESTStorage object that will work with ConfigMap objects.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
    store := &genericregistry.Store{
        NewFunc:                  func() runtime.Object { return &api.ConfigMap{} },
        NewListFunc:              func() runtime.Object { return &api.ConfigMapList{} },
        DefaultQualifiedResource: api.Resource("configmaps"),

        CreateStrategy: configmap.Strategy,
        UpdateStrategy: configmap.Strategy,
        DeleteStrategy: configmap.Strategy,
    }
    options := &generic.StoreOptions{RESTOptions: optsGetter}
    if err := store.CompleteWithOptions(options); err != nil {
        panic(err) // TODO: Propagate error up
    }
    return &REST{store}
}複製代碼

state

狀態模式(State Pattern) :容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。其別名爲狀態對象(Objects for States),狀態模式是一種對象行爲型模式。
分離狀態和行爲,比方說一個狀態機的實現,就是一個標準的state 模式

// k8s.io/kubernetes/vendor/github.com/coreos/etcd/raft/node.go
// Node represents a node in a raft cluster.
type Node interface {
    ....
    Step(ctx context.Context, msg pb.Message) error

    ....

    // Status returns the current status of the raft state machine.
    Status() Status
    ....
}複製代碼

memento

在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣就能夠將該對象恢復到原先保存的狀態

// k8s.io/kubernetes/pkg/registry/core/service/portallocator/allocator.go
// NewFromSnapshot allocates a PortAllocator and initializes it from a snapshot.
func NewFromSnapshot(snap *api.RangeAllocation) (*PortAllocator, error) {
    pr, err := net.ParsePortRange(snap.Range)
    if err != nil {
        return nil, err
    }
    r := NewPortAllocator(*pr)
    if err := r.Restore(*pr, snap.Data); err != nil {
        return nil, err
    }
    return r, nil
}

func (r *PortAllocator) Snapshot(dst *api.RangeAllocation) error {
    snapshottable, ok := r.alloc.(allocator.Snapshottable)
    if !ok {
        return fmt.Errorf("not a snapshottable allocator")
    }
    rangeString, data := snapshottable.Snapshot()
    dst.Range = rangeString
    dst.Data = data
    return nil
}複製代碼

廣義上看,deployment,statefulset等對象實現了rollback方法,也是相似memeto,好比deployment有各類version的replicaset的history備份,用於恢復歷史版本。詳細見k8s.io/kubernetes/pkg/controller/deployment/

flyweight / object pool

flyweight強調的是對象複用,和object pool的目的是同樣的。
One difference in that flyweights are commonly immutable instances, while resources acquired from the pool usually are mutable.
object pool在golang和kubernetes用得比較多,好比官方就有sync.pool

// k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/cacher.go

var timerPool sync.Pool

func (c *cacheWatcher) add(event *watchCacheEvent, budget *timeBudget) {
    ...

    t, ok := timerPool.Get().(*time.Timer)
    if ok {
        t.Reset(timeout)
    } else {
        t = time.NewTimer(timeout)
    }
    defer timerPool.Put(t)

    ....
}複製代碼

iterpreter

解釋器模式定義一套語言文法,並設計該語言解釋器,使用戶能使用特定文法控制解釋器行爲。
這個在kubernetes的各類代碼/文檔生成工具中用得不少。

chain_of_responsibility

也是一種組合模式.用的地方不少。職責鏈模式用於分離不一樣職責,而且動態組合相關職責。鏈對象包含當前職責對象以及下一個職責鏈。

// 這種wrapper 把函數處理交給childrens
// k8s.io/test-infra/velodrome/transform/plugins/multiplexer_wrapper.go
func NewMultiplexerPluginWrapper(plugins ...Plugin) *MultiplexerPluginWrapper {
    return &MultiplexerPluginWrapper{
        plugins: plugins,
    }
}

// 這種warpper 本身處理完了之後再交給clildrens
// k8s.io/test-infra/velodrome/transform/plugins/author_logger_wrapper.go
func NewAuthorLoggerPluginWrapper(plugin Plugin) *AuthorLoggerPluginWrapper {
    return &AuthorLoggerPluginWrapper{
        plugin: plugin,
    }
}

// 本身不處理,交給clildrens
...複製代碼

visit

對象只要預留訪問者接口(如Accept)則後期爲對象添加功能的時候就不須要改動對象。本質就是讓外部能夠定義一種visit本身對象的方法,從這個角度看,只要visit做爲一個函數傳遞就是一個visit模式。即

func(a *A)Visit(Vistor func(*A) error){
    Vistor(a)
}複製代碼

例子

// k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/openapi.go
type SchemaVisitor interface {
    VisitArray(*Array)
    VisitMap(*Map)
    VisitPrimitive(*Primitive)
    VisitKind(*Kind)
    VisitReference(Reference)
}

// Schema is the base definition of an openapi type.
type Schema interface {
    // Giving a visitor here will let you visit the actual type.
    Accept(SchemaVisitor)

    // Pretty print the name of the type.
    GetName() string
    // Describes how to access this field.
    GetPath() *Path
    // Describes the field.
    GetDescription() string
    // Returns type extensions.
    GetExtensions() map[string]interface{}
}複製代碼
// /k8s.io/kubernetes/pkg/kubectl/resource/builder.go
func (b *Builder) visitByName() *Result {
    ...

    visitors := []Visitor{}
    for _, name := range b.names {
        info := NewInfo(client, mapping, selectorNamespace, name, b.export)
        visitors = append(visitors, info)
    }
    result.visitor = VisitorList(visitors)
    result.sources = visitors
    return result
}複製代碼

engineering patterns

code generator

相關文章
相關標籤/搜索