云原生技術(shù)分享不僅僅局限于Go
、Rust
、Python
、Istio
、containerd
、CoreDNS
、Envoy
、etcd
、Fluentd
、Harbor
、Helm
、Jaeger
、Kubernetes
、OpenPolicyAgent
、Prometheus
、Rook
、TiKV
、TUF
、Vitess
、Arg
o
、Buildpacks
、CloudEvents
、CNI
、Contour
、Cortex
、CRI-O
、Falco
、Flux
、gRPC
、KubeEdge
、Linkerd
、NATS
、Notary
、OpenTracing
、Operator
Framework
、SPIFFE
、SPIRE
和Thanos
等
Go 中的構(gòu)建器模式
在 Operator
條件更新上應(yīng)用 Go
風(fēng)格的構(gòu)建器模式的實(shí)際示例
建議我們?cè)谀硞€(gè)“框架”內(nèi)進(jìn)行編碼,即遵循一定的設(shè)計(jì)模式,這些模式是有效的、可復(fù)制的、被廣泛認(rèn)可的、更容易理解和應(yīng)用的。
為什么要設(shè)計(jì)模式
為了不那么抽象,我們從實(shí)踐中的一個(gè)例子開始。
通常,我們定義一個(gè)struct
,然后在使用它時(shí)對(duì)其進(jìn)行初始化。
typeAstruct{
namestring
}
a:=A{name:“abc”}
這是常見的用法,但不適用于A
復(fù)雜的場景
- 多層嵌套字段
- 超過 5 個(gè)字段
- 不同的字段需要不同的默認(rèn)值
- 多個(gè)可選字段
- 以上四種的組合
例如在Kubernetes operator
開發(fā)中,我們調(diào)用SetStatusAndCondition
來更新資源信息,其中不僅包含了metav1
的基本信息。條件,如狀態(tài),原因,觀察生成等,但也傳遞回調(diào)函數(shù),如OnSuccess
和OnError
。圍繞ConditionAndStatus
,我們可以添加其他邏輯,比如發(fā)送事件、處理不同狀態(tài)(成功或失敗)的邏輯,等等,然后定義一個(gè)類似如下的結(jié)構(gòu)。
typeConditionAndStatusstruct{
Conditionmetav1.Condition
EventTypestring//eventtype
Recorderrecord.EventRecorder,//K8seventsrecorder
Forcebool,//isForceupdate
OnErrorfunc,//errhandler
OnSuccesfunc,//successhandler
}
它可以通過通過new
初始化這個(gè)ConditionAndStatus
來工作,但是當(dāng)有超過5
個(gè)字段并且其中兩個(gè)是嵌套的時(shí)候,它是累贅和復(fù)雜的,這是對(duì)非調(diào)用者友好的,并且在代碼可讀性上很差。除非condition
和eventRecorder
被實(shí)例化,否則調(diào)用者不能實(shí)例化ConditionAndStatus
。調(diào)用者需要知道所有的實(shí)現(xiàn)細(xì)節(jié),例如,他們應(yīng)該知道在錯(cuò)誤處理中更新條件時(shí)傳遞onSucc
方法,即使只有nil
。此外,不同的用戶在不同的地方執(zhí)行初始化時(shí),每次都需要傳入相同的onSucc
和onErr
。
那么我們?cè)撊绾蝺?yōu)化這段代碼呢?
Factory 模式
應(yīng)用Factory
模式可能是我們想到的第一個(gè)想法,但它不適用于這種情況。
通過Factory
模式封裝一些創(chuàng)建方法。
//Createnofalse,nodefaulthandlers
func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder)ConditionAndStatus{
returncreate(cond,eventType,recorder,false,nil,nil)
}
//Createnodefaulthandlers
func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,forcebool)ConditionAndStatus{
returncreate(cond,eventType,recorder,force,nil,nil)
}
//...morecreatefunctions
func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,forcebool,onErrfunc,onSuccfunc)ConditionAndStatus{
returnConditionAndStatus{
condtion:cond,
eventType:eventType,
recorder:recorder,
force:force,
onErr:onErr,
onSucces:onSucc,
}
}
api
應(yīng)該易于使用且不易誤用——來自Josh Bloch
然而,Factory
模式實(shí)現(xiàn)的api
并不是那么方便。
顯然,create
不是一個(gè)選項(xiàng),因?yàn)樗枰峁┧?a target="_blank">參數(shù),傳入的參數(shù)越多,操作就越困難。此外,當(dāng)多個(gè)參數(shù)為同一類型時(shí),很容易出錯(cuò)。
盡管其他Factory
方法可以通過提供一些默認(rèn)值來減少傳入的參數(shù)來降低復(fù)雜性,但它們?nèi)匀蝗狈`活性。添加參數(shù)后,需要修改所有create *
方法。
Builder模式
Builder
模式是一種設(shè)計(jì)模式,旨在為面向?qū)ο?a href="http://www.wenjunhu.com/v/tag/1315/" target="_blank">編程中的各種對(duì)象創(chuàng)建問題提供靈活的解決方案
來自https://en.wikipedia.org/wiki/Builder_pattern。
構(gòu)建器模式為靈活簡化復(fù)雜對(duì)象的創(chuàng)建鋪平了道路,同時(shí)也隱藏了嵌入式類型的一些初始化細(xì)節(jié),大大提高了可讀性。
Builder接口
builder
接口是兩種builder
模式實(shí)現(xiàn)之一,buildxxx
用接口實(shí)現(xiàn)各個(gè)字段的方法,Builder
通過多態(tài)性確定具體的builder
。請(qǐng)參閱下面的 UML
流程圖。
讓我們“翻新”以前的ConditionAndStatus
.
typeConditionAndStatusBuilderinterface{
SetCondtion(condmetav1.Condition)ConditionAndStatusBuilder
SetEventType(evnetTypestring)ConditionAndStatusBuilder
SetRecorder(recorderrecord.EventRecorder)ConditionAndStatusBuilder
SetForce(forcebool)ConditionAndStatusBuilder
SetOnErr(onErrfunc())ConditionAndStatusBuilder
SetOnSuccess(onSuccfunc())ConditionAndStatusBuilder
Build()ConditionAndStatus
}
typeDefaultBuilderstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}
func(b*DefaultBuilder)SetCondtion(condmetav1.Condition)DefaultBuilder{
b.condition=cond
returnb
}
//...moresetfuncs
func(b*DefaultBuilder)Build()ConditionAndStatus{
//setsomedefaultvalues
b.force=true
returnConditionAndStatus{
condition:b.condtion,
//...
}
}
要?jiǎng)?chuàng)建ConditionAndStatus
,您可以使用注冊(cè)方法組成所有構(gòu)建器,然后通過getByName
獲得特定的構(gòu)建器。
不難得出結(jié)論,該模式與Factory
模式非常相似,因?yàn)槊總€(gè)構(gòu)建器仍然需要?jiǎng)?chuàng)建所有字段或提供默認(rèn)值。但它確實(shí)向前邁出了一步。
- 當(dāng)字段確定時(shí),它可以靈活地添加新的生成器,而不需要修改舊的生成器。
- 它可以控制創(chuàng)建不同字段的順序。如果字段是相互依賴的,它可以隱藏細(xì)節(jié)并防止調(diào)用者犯錯(cuò)誤。
然而,它與Factory
模式有相同的缺點(diǎn):一旦添加了字段(在Builder
接口中添加方法),就需要修改所有構(gòu)建器。
Pipeline建設(shè)者
另一種構(gòu)建器模式是管道構(gòu)建器,它更常見。通過上面的接口builder
,你會(huì)發(fā)現(xiàn)多builder
的設(shè)計(jì)是多余的,而讓調(diào)用者控制相關(guān)字段的分配更合理:唯一的一個(gè)builder
管理所有字段初始化,并通過返回builder
本身來構(gòu)建管道在每一步中,最后都組裝成我們想要的。
通用調(diào)用代碼的格式為obj.Withxxx().Withyyy().Withzzz().build()
. 更改ConditionAndStatus
如下。
typeBuilderstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}
func(b*Builder)WithCondition(condmetav1.Condition)Builder{
b.condition=cond
returnb
}
//...moreWithxxxfuncs
func(b*Builder)Build()ConditionAndStatus{
//setsomedefaultvalues
b.force=true
returnConditionAndStatus{
condition:b.condtion,
//...
}
}
Pipeline builder
巧妙地避免了添加新字段帶來的麻煩。只有一個(gè)builder
,它可以通過添加With*
方法輕松處理字段添加。
它對(duì)現(xiàn)有的調(diào)用者絕對(duì)更友好。如果參數(shù)是可選的,則不需要修改其他調(diào)用者的代碼。而你只有通過添加新的調(diào)用者并With*
在調(diào)用時(shí)插入方法來完成它;但是,當(dāng)需要新參數(shù)而沒有默認(rèn)值時(shí),則需要修改所有調(diào)用者的代碼。
當(dāng)然,沒有一種模式是沒有缺陷的,管道構(gòu)建器也不是。
-
Withxxx()
一旦要構(gòu)建許多字段,堆積的方法會(huì)給調(diào)用者帶來麻煩并降低可讀性。 - 無法控制字段的初始化順序。如果存在依賴關(guān)系,則需要出色的錯(cuò)誤控制和文檔來避免錯(cuò)誤。
-
代碼不是
Go
風(fēng)格,而是更多Java
風(fēng)格。
可選的構(gòu)建器模式
如果我們進(jìn)一步優(yōu)化管道構(gòu)建器會(huì)怎樣?正如Dave Cheney
在他的Practical Go
中提到的那樣,我們應(yīng)該以更多 Go
的方式嘗試它。
首選 var args
到 []T
參數(shù)
深入挖掘,我們看到這里的大部分字段都是可選的,并且可以var args
自然地定義。如有傳入,申報(bào);如果沒有,請(qǐng)忽略它。因此,builder/factory
當(dāng)隱藏實(shí)現(xiàn)細(xì)節(jié)時(shí),只需要一種方法來處理整個(gè)對(duì)象的創(chuàng)建。
讓我們一步一步地把這個(gè)想法付諸實(shí)踐。
將可選字段抽象到構(gòu)建結(jié)構(gòu)中,而不是將所有字段都放入。要將ConditionAndStatus
轉(zhuǎn)換為以下結(jié)構(gòu),其中配置包含所有可選字段。
typeConditionAndStatusstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
configsconfigs//Optionalconfigs
}
typeconfigsstruct{
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}
對(duì)于配置,使用func
選項(xiàng)接受一個(gè)*configs
并返回自身以集成到管道中。需要使用以下方法。
typeconfigsstruct{
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}
typeOptionfunc(*configs)
funcForceUpdate(forcebool)Option{returnfunc(c*configs){c.force=force}}
funcOnErr(onErrfunc())Option{returnfunc(c*configs){c.onErr=onErr}}
funcOnSuccess(onSuccfunc())Option{returnfunc(c*configs){c.onSuccess=onSucc}}
然后是新的create
方法,包括必要字段和可選配置的初始化。因?yàn)樗锌蛇x的配置都是用func
類型的返回值初始化的,所以整個(gè)配置的賦值只能用一個(gè)循環(huán)來完成。超級(jí)簡潔!
funcCreate(conditionmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,os...Option)error{
opts:=configs{
force:false,
onSuccess:func(){},
onError:nil,
}
//Applyalltheoptionalconfigs
for_,applyOption:=rangeos{
applyOption(&opts)
}
//checkrequiredfields
//updateconditionshere
//handleerr
ifopts.err!=nil{
returnopts.onError()
}
//eveutallycallsuccessfunc
opts.onSuccess()
}
調(diào)用方可以根據(jù)場景選擇可選配置,避免誤用。
setCondition(
metav1.Condition{
Type:apis.Ready,
Status:metav1.ConditionFalse,
Reason:apis.UpstreamUnavailable,
Message:fmt.Sprintf("Failedtosetresources%#v",resource),
},
"Update",
nil,
//onlyneedonErrfuncfromtheoptionalconfigs.
conditionAndStatus.ForOnErr(err),
)
Builder in Kubernetes
在Kubernetes
源代碼的幾乎每個(gè)角落都可以看到這種go
風(fēng)格的代碼。幾乎所有的結(jié)構(gòu)被*配置是建立在可選的建造者模式,如PodApplyConfiguration EventApplyConfiguration
和配置文檔你找到包裹。這些逐層嵌套配置獲得最終值與一個(gè)或多個(gè)方法類似于PodApplyConfiguration
提取。
最后
設(shè)計(jì)模式是經(jīng)典的,盡管不是所有的模式都能在Go
中完美實(shí)現(xiàn)。Builder
無疑是其中最杰出的一個(gè),我們應(yīng)該最大限度地利用Optional
管道生成器模式來構(gòu)建一些配置對(duì)象,特別是在設(shè)計(jì)公共模塊時(shí)。使用靈活、遵守代碼標(biāo)準(zhǔn)和擴(kuò)展友好的api
,可以大大減輕升級(jí)壓力。
感謝你的閱讀!
原文標(biāo)題:Go 中的構(gòu)建器模式
文章出處:【微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
源代碼
+關(guān)注
關(guān)注
96文章
2946瀏覽量
66842 -
設(shè)計(jì)模式
+關(guān)注
關(guān)注
0文章
53瀏覽量
8646 -
Struct
+關(guān)注
關(guān)注
0文章
31瀏覽量
10886 -
云原生
+關(guān)注
關(guān)注
0文章
251瀏覽量
7964
原文標(biāo)題:Go 中的構(gòu)建器模式
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論