design pattern 介绍设计模式
engineer pattern 总结先进的工程模式
参考
对golang来说就是Newxx函数,返回interface, kubernetes interface随处可见,可以说能用interface抽象的就是interface,随便举一个例子
// 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 }
组合模式的不同形式,这些随处可见,也不用深究其中的区别。
kubernetes/golang 用得很少,一般使用全局变量(如配置) (比如 net/http package 的 http.DefaultClient 和 http.DefaultServeMux). 或者作为context传递。
实现方式可以参考http://marcio.io/2015/07/singleton-pattern-in-go/
比较常见的一种方式是double check
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 }
关于这几种creational patterns 的区别:
factory
// 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 }
原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。
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.
参考http://blog.ralch.com/tutorial/design-patterns/golang-prototype/
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
使用 https://github.com/kubernetes/gengo
这个模式在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 Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
kubernetes 的command 基于 github.com/spf13/cobra, 把命令变成了对象,比如cmdRollOut还实现了undo。
和数据结构相关的package里面用得比较多.如引用的库jsoniter, btree, govalidator.
// golang标准库 "go/token" 实现了Iterate方法,但是其实这里更像vistor模式... func (s *FileSet) Iterate(f func(*File) bool)
定义一系列算法,让这些算法在运行时可以互换,使得分离算法,符合开闭原则。对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。
实际上使用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 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 .... }
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态
// 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的目的是一样的。
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) .... }
解释器模式定义一套语言文法,并设计该语言解释器,使用户能使用特定文法控制解释器行为。
这个在kubernetes的各种代码/文档生成工具中用得很多。
也是一种组合模式.用的地方很多。职责链模式用于分离不同职责,并且动态组合相关职责。链对象包含当前职责对象以及下一个职责链。
// 这种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 ...
对象只要预留访问者接口(如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 }
原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
如有侵权,请联系 yunjia_community@tencent.com 删除。
我来说两句