通過這篇文章《為什麼說Go的函數是」一等公民「》,我們瞭解到了什麼是「一等公民」,以及都具備哪些特性,同時對函數的基本使用也更加深入。
本文重點介紹下Go設計模式之函數選項模式,它得益於Go的函數是「一等公民」,很好的一個應用場景,廣泛被使用。
函數選項模式(Functional Options Pattern) ,也稱為選項模式(Options Pattern),是一種創造性的設計模式,允許你使用接受零個或多個函數作為引數的可變建構函式來構建複雜結構。我們將這些函數稱為選項,由此得名函數選項模式。
看概念有點太生硬難懂了,下面通過例子來講解下怎麼使用,由淺入深,通俗易懂。
先來一個簡單例子,這個Animal結構體
,怎麼構造出一個範例物件
type Animal struct {
Name string
Age int
Height int
}
通常的寫法:
func NewAnimal(name string, age int, height int) *Animal {
return &Animal{
Name: name,
Age: age,
Height: height,
}
}
a1 := NewAnimal("小白兔", 5, 100)
簡單易懂,結構體有哪些屬性欄位,那麼建構函式的引數,就相應做定義並傳入
帶來的問題:
例如,現計劃新加3個欄位Weight體重
、CanRun是否會跑
、LegNum幾條腿
,同時要指定預設值CanRun=true、LegNum=4
新結構體定義:
type Animal struct {
Name string
Age int
Height int
Weight int
CanRun bool
LegNum int
}
程式碼實現(函數加新引數定義,但預設值貌似實現不了,得呼叫建構函式時,明確傳入):
func NewAnimal(name string, age int, height int, weight int, canRun bool, legNum int) *Animal {
return &Animal{
Name: name,
Age: age,
Height: height,
Weight: weight,
CanRun: canRun,
LegNum: legNum,
}
}
a1 := NewAnimal("小白兔", 5, 100, 120, true, 4)
後續逐步加新欄位,這個建構函式就會被撐爆了,如果呼叫的地方越多,那麼越傷筋動骨。
既然常規寫法太low,難以實現新需求,那麼我們就來玩點高階的,引出主題:函數選項模式
首先,需要先定義一個函數型別OptionFunc
type OptionFunc func(*Animal)
然後,根據新結構體欄位,定義With開頭
的函數,返回函數型別為OptionFunc
的閉包函數,內部邏輯只需要實現更新對應欄位值即可
func WithName(name string) OptionFunc {
return func(a *Animal) { a.Name = name }
}
func WithAge(age int) OptionFunc {
return func(a *Animal) { a.Age = age }
}
func WithHeight(height int) OptionFunc {
return func(a *Animal) { a.Height = height }
}
func WithWeight(weight int) OptionFunc {
return func(a *Animal) { a.Weight = weight }
}
func WithCanRun(canRun bool) OptionFunc {
return func(a *Animal) { a.CanRun = canRun }
}
func WithLegNum(legNum int) OptionFunc {
return func(a *Animal) { a.LegNum = legNum }
}
再然後,優化建構函式的定義和實現(name作為必傳引數,其他可選,並且實現CanRun
和LegNum
兩個欄位指定預設值)
func NewAnimal(name string, opts ...OptionFunc) *Animal {
a := &Animal{Name: name, CanRun: true, LegNum: 4}
for _, opt := range opts {
opt(a)
}
return a
}
最後,呼叫優化後的建構函式,快速實現範例的初始化。想要指定哪個欄位值,那就呼叫相應的With開頭
的函數,完全做到可設定化、可插拔;不指定還支援了預設值
a2 := NewAnimal("大黃狗", WithAge(10), WithHeight(120))
fmt.Println(a2)
a3 := NewAnimal("大灰狼", WithHeight(200))
fmt.Println(a3)
輸出結果:
&{大黃狗 10 120 0 true 4}
&{大灰狼 0 200 0 true 4}
帶來的好處:
函數選項模式,不單單是我們業務程式碼中有使用,現在大量的標準庫和第三庫都在使用。
下面帶著大家一塊來看看,apollo設定中心使用者端第三庫shima-park/agollo,看看它是怎麼玩的,怎麼做設定初始化
核心程式碼:
type Options struct {
AppID string // appid
Cluster string // 預設的叢集名稱,預設:default
DefaultNamespace string // Get時預設使用的名稱空間,如果設定了該值,而不在PreloadNamespaces中,預設也會加入初始化邏輯中
PreloadNamespaces []string // 預載入名稱空間,預設:為空
ApolloClient ApolloClient // apollo HTTP api實現
Logger Logger // 紀錄檔實現類,可以設定自定義實現或者通過NewLogger()建立並設定有效的io.Writer,預設: ioutil.Discard
AutoFetchOnCacheMiss bool // 自動獲取非預設以外的Namespace的設定,預設:false
LongPollerInterval time.Duration // 輪訓間隔時間,預設:1s
BackupFile string // 備份檔案存放地址,預設:.agollo
FailTolerantOnBackupExists bool // 伺服器連線失敗時允許讀取備份,預設:false
Balancer Balancer // ConfigServer負載均衡
EnableSLB bool // 啟用ConfigServer負載均衡
RefreshIntervalInSecond time.Duration // ConfigServer重新整理間隔
ClientOptions []ApolloClientOption // 設定apollo HTTP api的設定項
EnableHeartBeat bool // 是否允許兜底檢查,預設:false
HeartBeatInterval time.Duration // 兜底檢查間隔時間,預設:300s
}
func newOptions(configServerURL, appID string, opts ...Option) (Options, error) {
var options = Options{
AppID: appID,
Cluster: defaultCluster,
ApolloClient: NewApolloClient(),
Logger: NewLogger(),
AutoFetchOnCacheMiss: defaultAutoFetchOnCacheMiss,
LongPollerInterval: defaultLongPollInterval,
BackupFile: defaultBackupFile,
FailTolerantOnBackupExists: defaultFailTolerantOnBackupExists,
EnableSLB: defaultEnableSLB,
EnableHeartBeat: defaultEnableHeartBeat,
HeartBeatInterval: defaultHeartBeatInterval,
}
for _, opt := range opts {
opt(&options)
}
//...省略
return options, nil
}
type Option func(*Options)
//一系列函數作為選項
func PreloadNamespaces(namespaces ...string) Option {
return func(o *Options) {
o.PreloadNamespaces = append(o.PreloadNamespaces, namespaces...)
}
}
func AutoFetchOnCacheMiss() Option {
return func(o *Options) {
o.AutoFetchOnCacheMiss = true
}
}
//...
玩法:
由淺入深的講解了下範例物件初始化一般寫法和高階寫法。用好這個高階寫法(函數選項模式),讓程式碼更優雅。
還不會使用的Gopher,趕緊學起來,用起來。
文章首發在公眾號:程式設計師升職加薪之旅,歡迎大家關注,第一時間收到新內容。