<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go編程模式:詳解函數(shù)式選項(xiàng)模式

          共 4820字,需瀏覽 10分鐘

           ·

          2021-11-29 18:07

          閱讀本文大概需要 8?分鐘。

          大家好,我是 polarisxu。

          Go 不是完全面向?qū)ο笳Z(yǔ)言,有一些面向?qū)ο竽J讲惶m合它。但經(jīng)過(guò)這些年的發(fā)展,Go 有自己的一些模式。今天介紹一個(gè)常見(jiàn)的模式:函數(shù)式選項(xiàng)模式(Functional Options Pattern)。

          01 什么是函數(shù)式選項(xiàng)模式

          Go 語(yǔ)言沒(méi)有構(gòu)造函數(shù),一般通過(guò)定義 New 函數(shù)來(lái)充當(dāng)構(gòu)造函數(shù)。然而,如果結(jié)構(gòu)有較多字段,要初始化這些字段,有很多種方式,但有一種方式認(rèn)為是最好的,這就是函數(shù)式選項(xiàng)模式(Functional Options Pattern)。

          函數(shù)式選項(xiàng)模式是一種在 Go 中構(gòu)造結(jié)構(gòu)體的模式,它通過(guò)設(shè)計(jì)一組非常有表現(xiàn)力和靈活的 API 來(lái)幫助配置和初始化結(jié)構(gòu)體。

          在 Uber 的 Go 語(yǔ)言規(guī)范中提到了該模式:

          Functional options 是一種模式,在該模式中,你可以聲明一個(gè)不透明的 Option 類型,該類型在某些內(nèi)部結(jié)構(gòu)中記錄信息。你接受這些可變數(shù)量的選項(xiàng),并根據(jù)內(nèi)部結(jié)構(gòu)上的選項(xiàng)記錄的完整信息進(jìn)行操作。

          將此模式用于構(gòu)造函數(shù)和其他公共 API 中的可選參數(shù),你預(yù)計(jì)這些參數(shù)需要擴(kuò)展,尤其是在這些函數(shù)上已經(jīng)有三個(gè)或更多參數(shù)的情況下。

          02 一個(gè)示例

          為了更好的理解該模式,我們通過(guò)一個(gè)例子來(lái)講解。

          定義一個(gè) Server 結(jié)構(gòu)體:

          package?main

          type?Server?{
          ??host?string
          ??port?int
          }

          func?New(host?string,?port?int)?*Server?{
          ??return?&Server{host,?port}
          }

          func?(s?*Server)?Start()?error?{
          }

          如何使用呢?

          package?main

          import?(
          ??"log"
          ??"server"
          )

          func?main()?{
          ??svr?:=?New("localhost",?1234)
          ??if?err?:=?svr.Start();?err?!=?nil?{
          ????log.Fatal(err)
          ??}
          }

          但如果要擴(kuò)展 Server 的配置選項(xiàng),如何做?通常有三種做法:

          • 為每個(gè)不同的配置選項(xiàng)聲明一個(gè)新的構(gòu)造函數(shù)
          • 定義一個(gè)新的 Config 結(jié)構(gòu)體來(lái)保存配置信息
          • 使用 Functional Option Pattern

          做法 1:為每個(gè)不同的配置選項(xiàng)聲明一個(gè)新的構(gòu)造函數(shù)

          這種做法是為不同選項(xiàng)定義專有的構(gòu)造函數(shù)。假如上面的 Server 增加了兩個(gè)字段:

          type?Server?{
          ??host?string
          ??port?int
          ??timeout?time.Duration
          ??maxConn?int
          }

          一般來(lái)說(shuō),host 和 port 是必須的字段,而 timeout 和 maxConn 是可選的,所以,可以保留原來(lái)的構(gòu)造函數(shù),而這兩個(gè)字段給默認(rèn)值:

          func?New(host?string,?port?int)?*Server?{
          ??return?&Server{host,?port,?time.Minute,?100}
          }

          然后針對(duì) timeout 和 maxConn 額外提供兩個(gè)構(gòu)造函數(shù):

          func?NewWithTimeout(host?string,?port?int,?timeout?time.Duration)?*Server?{
          ??return?&Server{host,?port,?timeout}
          }

          func?NewWithTimeoutAndMaxConn(host?string,?port?int,?timeout?time.Duration,?maxConn?int)?*Server?{
          ??return?&Server{host,?port,?timeout,?maxConn}
          }

          這種方式配置較少且不太會(huì)變化的情況,否則每次你需要為新配置創(chuàng)建新的構(gòu)造函數(shù)。在 Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中,有這種方式的應(yīng)用。比如 net 包中的 Dial 和 DialTimeout:

          func?Dial(network,?address?string)?(Conn,?error)
          func?DialTimeout(network,?address?string,?timeout?time.Duration)?(Conn,?error)

          做法 2:使用專門(mén)的配置結(jié)構(gòu)體

          這種方式也是很常見(jiàn)的,特別是當(dāng)配置選項(xiàng)很多時(shí)。通??梢詣?chuàng)建一個(gè) Config 結(jié)構(gòu)體,其中包含 Server 的所有配置選項(xiàng)。這種做法,即使將來(lái)增加更多配置選項(xiàng),也可以輕松的完成擴(kuò)展,不會(huì)破壞 Server 的 API。

          type?Server?{
          ??cfg?Config
          }

          type?Config?struct?{
          ??Host?string
          ??Port?int
          ??Timeout?time.Duration
          ??MaxConn?int
          }

          func?New(cfg?Config)?*Server?{
          ??return?&Server{cfg}
          }

          在使用時(shí),需要先構(gòu)造 Config 實(shí)例,對(duì)這個(gè)實(shí)例,又回到了前面 Server 的問(wèn)題上,因?yàn)樵黾踊騽h除選項(xiàng),需要對(duì) Config 有較大的修改。如果將 Config 中的字段改為私有,可能需要定義 Config 的構(gòu)造函數(shù)。。。

          做法 3:使用 Functional Option Pattern

          一個(gè)更好的解決方案是使用 Functional Option Pattern。

          在這個(gè)模式中,我們定義一個(gè) Option 函數(shù)類型:

          type?Option?func(*Server)

          Option 類型是一個(gè)函數(shù)類型,它接收一個(gè)參數(shù):*Server。然后,Server 的構(gòu)造函數(shù)接收一個(gè) Option 類型的不定參數(shù):

          func?New(options?...Option)?*Server?{
          ??svr?:=?&Server{}
          ??for?_,?f?:=?range?options?{
          ????f(svr)
          ??}
          ??return?svr
          }

          那選項(xiàng)如何起作用?需要定義一系列相關(guān)返回 Option 的函數(shù):

          func?WithHost(host?string)?Option?{
          ??return?func(s?*Server)?{
          ????s.host?=?host
          ??}
          }

          func?WithPort(port?int)?Option?{
          ??return?func(s?*Server)?{
          ????s.port?=?port
          ??}
          }

          func?WithTimeout(timeout?time.Duration)?Option?{
          ??return?func(s?*Server)?{
          ????s.timeout?=?timeout
          ??}
          }

          func?WithMaxConn(maxConn?int)?Option?{
          ??return?func(s?*Server)?{
          ????s.maxConn?=?maxConn
          ??}
          }

          針對(duì)這種模式,客戶端類似這么使用:

          package?main

          import?(
          ??"log"
          ??
          ??"server"
          )

          func?main()?{
          ??svr?:=?New(
          ????WithHost("localhost"),
          ????WithPort(8080),
          ????WithTimeout(time.Minute),
          ????WithMaxConn(120),
          ??)
          ??if?err?:=?svr.Start();?err?!=?nil?{
          ????log.Fatal(err)
          ??}
          }

          將來(lái)增加選項(xiàng),只需要增加對(duì)應(yīng)的 WithXXX 函數(shù)即可。

          這種模式,在第三方庫(kù)中使用挺多,比如 github.com/gocolly/colly:

          type?Collector?{
          ??//?省略...
          }
          func?NewCollector(options?...CollectorOption)?*Collector

          //?定義了一系列?CollectorOpiton
          type?CollectorOption{
          ??//?省略...
          }
          func?AllowURLRevisit()?CollectorOption
          func?AllowedDomains(domains?...string)?CollectorOption
          ...

          不過(guò) Uber 的 Go 語(yǔ)言編程規(guī)范中提到該模式時(shí),建議定義一個(gè) Option 接口,而不是 Option 函數(shù)類型。該 Option 接口有一個(gè)未導(dǎo)出的方法,然后通過(guò)一個(gè)未導(dǎo)出的 options 結(jié)構(gòu)來(lái)記錄各選項(xiàng)。

          Uber 的這個(gè)例子能看懂嗎?

          type?options?struct?{
          ??cache??bool
          ??logger?*zap.Logger
          }

          type?Option?interface?{
          ??apply(*options)
          }

          type?cacheOption?bool

          func?(c?cacheOption)?apply(opts?*options)?{
          ??opts.cache?=?bool(c)
          }

          func?WithCache(c?bool)?Option?{
          ??return?cacheOption(c)
          }

          type?loggerOption?struct?{
          ??Log?*zap.Logger
          }

          func?(l?loggerOption)?apply(opts?*options)?{
          ??opts.logger?=?l.Log
          }

          func?WithLogger(log?*zap.Logger)?Option?{
          ??return?loggerOption{Log:?log}
          }

          //?Open?creates?a?connection.
          func?Open(
          ??addr?string,
          ??opts?...Option,
          )
          ?(*Connection,?error)
          ?{
          ??options?:=?options{
          ????cache:??defaultCache,
          ????logger:?zap.NewNop(),
          ??}

          ??for?_,?o?:=?range?opts?{
          ????o.apply(&options)
          ??}

          ??//?...
          }

          03 總結(jié)

          在實(shí)際項(xiàng)目中,當(dāng)你要處理的選項(xiàng)比較多,或者處理不同來(lái)源的選項(xiàng)(來(lái)自文件、來(lái)自環(huán)境變量等)時(shí),可以考慮試試函數(shù)式選項(xiàng)模式。

          注意,在實(shí)際工作中,我們不應(yīng)該教條的應(yīng)用上面的模式,就像 Uber 中的例子,Open 函數(shù)并非只接受一個(gè) Option 不定參數(shù),因?yàn)?addr 參數(shù)是必須的。因此,函數(shù)式選項(xiàng)模式更多應(yīng)該應(yīng)用在那些配置較多,且有可選參數(shù)的情況。

          參考文獻(xiàn)

          • https://golang.cafe/blog/golang-functional-options-pattern.html
          • https://github.com/uber-go/guide/blob/master/style.md#functional-options



          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開(kāi)源圖書(shū)《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。


          堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio


          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  十八禁在线免费观看网址 | 欧美综合播放网站在线观看 | 欧美丁香五月 | 狼友视频首页 | 在线观看黄色的网站 |