0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

為什么要設(shè)計(jì)模式

馬哥Linux運(yùn)維 ? 來源:云原生CTO ? 作者:charts ? 2022-06-14 11:08 ? 次閱讀

術(shù)GoRust、PythonIstio、containerdCoreDNS、Envoy、etcd、Fluentd、HarborHelm、Jaeger、KubernetesOpenPolicyAgent、Prometheus、Rook、TiKV、TUF、Vitess、Argo、Buildpacks、CloudEvents、CNI、ContourCortex、CRI-O、FalcoFlux、gRPCKubeEdge、Linkerd、NATS、Notary、OpenTracingOperatorFramework、SPIFFE、SPIREThanos


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ù),如OnSuccessOnError。圍繞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)用者友好的,并且在代碼可讀性上很差。除非conditioneventRecorder被實(shí)例化,否則調(diào)用者不能實(shí)例化ConditionAndStatus。調(diào)用者需要知道所有的實(shí)現(xiàn)細(xì)節(jié),例如,他們應(yīng)該知道在錯(cuò)誤處理中更新條件時(shí)傳遞onSucc方法,即使只有nil。此外,不同的用戶在不同的地方執(zhí)行初始化時(shí),每次都需要傳入相同的onSucconErr。

那么我們?cè)撊绾蝺?yōu)化這段代碼呢?

Factory 模式

應(yīng)用Factory模式可能是我們想到的第一個(gè)想法,但它不適用于這種情況。

5e4ce432-eb1e-11ec-ba43-dac502259ad0.png

通過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 流程圖。

5e5c6420-eb1e-11ec-ba43-dac502259ad0.png

讓我們“翻新”以前的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)建管道在每一步中,最后都組裝成我們想要的。

5e8f8990-eb1e-11ec-ba43-dac502259ad0.png

通用調(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)注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 源代碼
    +關(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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    為什么官方的移植DEMO下載到FLASH?STlink仿真模式問題?

    我最近在做STM32F107移植uCOSii,遇到幾個(gè)問題,為什么官方的移植DEMO下載到FLASH?在用STlink仿真時(shí),使用JATG模式出現(xiàn)錯(cuò)誤,而使用SWD就正常運(yùn)行呢?備注:編譯器
    發(fā)表于 03-27 15:04

    請(qǐng)問4通道都用DMA模式怎么使用

    我有4個(gè)AD要用到,想用DMA模式,看庫函數(shù)例程只有一個(gè)通道,請(qǐng)問4通道都用DMA模式怎么使用
    發(fā)表于 09-26 09:23

    在DCM下的ip要比CCM模式下的ip大出不少

    在DCM下的ip要比CCM模式下的ip大出不少的,同等條件下CCM模式有效值電流更低。有沒有詳細(xì)的推導(dǎo)公式假設(shè)輸出功率相同,開關(guān)頻率相同,同樣的輸入電壓
    發(fā)表于 10-23 11:01

    CAN初始化,CAN實(shí)驗(yàn)初始化,CAN_DeInit()這句話不寫可以嗎

    一些實(shí)驗(yàn),加了缺省設(shè)置,然后自己又配置了。感覺沒必要,CAN實(shí)驗(yàn)初始化,CAN_DeInit()這句話不寫也行把但很多實(shí)驗(yàn)都這樣寫,有什么好處?缺省設(shè)置不一定是你模式
    發(fā)表于 05-31 04:35

    STM32 IO口設(shè)置成什么模式

    stm32的部分管腳可以兼容5V,那么請(qǐng)問當(dāng)外接信號(hào)為5V作為輸入時(shí),IO口設(shè)置成什么模式?
    發(fā)表于 05-29 10:13

    為什么DAC端口設(shè)置為模擬輸入模式

    的 DAC 通道 1 在 PA4 上,所以,我們先要使能 PORTA 的時(shí)鐘,然后設(shè)置 PA4 為模擬輸入。DAC 本身是輸出,但是為什么端口設(shè)置為模擬輸入模式呢?因?yàn)橐坏鼓?DACx 通道之后
    發(fā)表于 08-17 07:22

    什么是設(shè)計(jì)模式?為什么學(xué)習(xí)設(shè)計(jì)模式

    物是人非事事休,當(dāng)周圍的一切都發(fā)生著改變,包括我們的需求、程序等,我們又該如何去應(yīng)對(duì)和解決呢?歡迎進(jìn)入編程人員必經(jīng)之路------設(shè)計(jì)模式1 本篇概述什么是設(shè)計(jì)模式為什么學(xué)習(xí)設(shè)計(jì)模式
    發(fā)表于 01-19 06:41

    CH9141默認(rèn)的應(yīng)該是從機(jī)模式,是設(shè)置成主機(jī)模式才能連接嗎?

    CH9141默認(rèn)的應(yīng)該是從機(jī)模式,是設(shè)置成主機(jī)模式才能連接嗎?
    發(fā)表于 09-21 07:33

    ESP32從AP切換到STA模式重啟么?

    ESP32上電先跑STA模式,接收到指令后切換AP模式進(jìn)入U(xiǎn)DP服務(wù)接收SSID PASSWORD,寫入NVS后再切回STA模式連接AP失敗怎么搞?STA模式下都是從NVS讀取
    發(fā)表于 03-09 06:22

    性能模式和省電模式的區(qū)別是什么

    大家都知道,Long long agoEMUI就有了不同的應(yīng)用模式,是為了兼顧不同的使用場景而為用戶設(shè)置的,例如玩游戲時(shí)需要性能強(qiáng),就需要性能模式,出差在外時(shí),盡量續(xù)航長,就選擇省電模式
    發(fā)表于 07-13 08:47 ?1w次閱讀

    什么是框架?MATLAB的單元測試框架中文版資料詳細(xì)概述

    從邏輯上來說,框架 (Framework),是?個(gè)??向?qū)ο蠛驮O(shè)計(jì)模式更加復(fù)雜的結(jié)構(gòu),但讀者不?擔(dān)?,雖然框架在結(jié)構(gòu)上?模式復(fù)雜,但是學(xué)習(xí)起來?設(shè)計(jì)
    發(fā)表于 11-18 08:00 ?0次下載
    什么是框架?MATLAB的單元測試框架中文版資料詳細(xì)概述

    手機(jī)雙屏模式如何設(shè)置

    最近小編發(fā)現(xiàn)有諸多的小伙伴們對(duì)于手機(jī)雙屏模式如何設(shè)置都頗為感興趣的,大家也都想要及時(shí)了解到手機(jī)雙屏模式如何設(shè)置相關(guān)信息
    的頭像 發(fā)表于 04-22 10:30 ?4.4w次閱讀

    三星顯示器調(diào)模式怎么調(diào)?

    通過顯示器的MENU按鍵,調(diào)出顯示器調(diào)節(jié)菜單,通過顯示器自帶的方向按鍵,調(diào)整光標(biāo)到【圖像】-【靈巧模式】中,選擇需要的顯示模式即可。
    的頭像 發(fā)表于 04-22 10:51 ?2.7w次閱讀

    為什么串口喚醒STOP模式?如何才能實(shí)現(xiàn)串口喚醒STOP模式呢?

    STM32常見的低功耗模式有三種:睡眠模式、STOP模式以及待機(jī)模式,STM32L系列還有其他低功耗模式。
    的頭像 發(fā)表于 06-06 11:02 ?4381次閱讀
    為什么<b class='flag-5'>要</b>串口喚醒STOP<b class='flag-5'>模式</b>?如何才能實(shí)現(xiàn)串口喚醒STOP<b class='flag-5'>模式</b>呢?

    什么是定長控制功能?變頻器如何設(shè)置定長控制模式?

    什么是定長控制功能?變頻器如何設(shè)置定長控制模式?變頻器定長模式的控制設(shè)置是如何工作的? 定長控制功能是變頻器控制系統(tǒng)中的一種模式,其主要作用是控制控制中所需軸的行程長度,從而實(shí)現(xiàn)工作
    的頭像 發(fā)表于 10-22 14:38 ?1698次閱讀