design pattern 介紹設計模式
engineer pattern 總結先進的工程模式node
參考git
對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
}複製代碼
組合模式的不一樣形式,這些隨處可見,也不用深究其中的區別。golang
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
}複製代碼
關於這幾種creational patterns 的區別:api
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
}複製代碼
原型模式是建立型模式的一種,其特色在於經過「複製」一個已經存在的實例來返回新的實例,而不是新建實例。被複制的實例就是咱們所稱的「原型」,這個原型是可定製的。
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.
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/…
這個模式在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
}複製代碼