1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 4820字,需瀏覽 10分鐘

         ·

        2021-11-29 18:07

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

        大家好,我是 polarisxu。

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

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

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

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

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

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

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

        02 一個示例

        為了更好的理解該模式,我們通過一個例子來講解。

        定義一個 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)
        ??}
        }

        但如果要擴展 Server 的配置選項,如何做?通常有三種做法:

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

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

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

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

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

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

        然后針對 timeout 和 maxConn 額外提供兩個構(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}
        }

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

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

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

        這種方式也是很常見的,特別是當配置選項很多時。通常可以創(chuàng)建一個 Config 結(jié)構(gòu)體,其中包含 Server 的所有配置選項。這種做法,即使將來增加更多配置選項,也可以輕松的完成擴展,不會破壞 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}
        }

        在使用時,需要先構(gòu)造 Config 實例,對這個實例,又回到了前面 Server 的問題上,因為增加或刪除選項,需要對 Config 有較大的修改。如果將 Config 中的字段改為私有,可能需要定義 Config 的構(gòu)造函數(shù)。。。

        做法 3:使用 Functional Option Pattern

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

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

        type?Option?func(*Server)

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

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

        那選項如何起作用?需要定義一系列相關(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
        ??}
        }

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

        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)
        ??}
        }

        將來增加選項,只需要增加對應(yīng)的 WithXXX 函數(shù)即可。

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

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

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

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

        Uber 的這個例子能看懂嗎?

        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é)

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

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

        參考文獻

        • 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)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。


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


        瀏覽 50
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美视频自拍 | 好爽好硬进去了好紧爽视频 | 996精品视频 | 一起操逼网 | 美女黄网站在线观看 | 97人人干| 在线黄色网 | 大巴车男人狂躁女人小说 | 亚洲中文字幕无码爆乳av | 亚洲精品欧美精品 |