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ù)選項(xiàng)模式

        共 10685字,需瀏覽 22分鐘

         ·

        2021-04-10 10:20

        點(diǎn)擊上方藍(lán)色“Go語言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

        作為 Golang 開發(fā)者,遇到的許多問題之一就是嘗試將函數(shù)的參數(shù)設(shè)置成可選項(xiàng)。這是一個(gè)十分常見的場(chǎng)景,您可以使用一些已經(jīng)設(shè)置默認(rèn)配置和開箱即用的對(duì)象,同時(shí)您也可以使用一些更為詳細(xì)的配置。

        對(duì)于許多編程語言來說,這很容易。在 C 語言家族中,您可以提供具有同一個(gè)函數(shù)但是不同參數(shù)的多個(gè)版本;在 PHP 之類的語言中,您可以為參數(shù)提供默認(rèn)值,并在調(diào)用該方法時(shí)將其忽略。但是在 Golang 中,上述的做法都不可以使用。那么您如何創(chuàng)建具有一些其他配置的函數(shù),用戶可以根據(jù)他的需求(但是僅在需要時(shí))指定一些額外的配置。

        有很多的方法可以做到這一點(diǎn),但是大多數(shù)方法都不是盡如人意,要么需要在服務(wù)端的代碼中進(jìn)行大量額外的檢查和驗(yàn)證,要么通過傳入他們不關(guān)心的其他參數(shù)來為客戶端進(jìn)行額外的工作。

        下面我將會(huì)介紹一些不同的選項(xiàng),然后為其說明為什么每個(gè)選項(xiàng)都不理想,接著我們會(huì)逐步構(gòu)建自己的方式來作為最終的干凈解決方案:函數(shù)選項(xiàng)模式。

        讓我們來看一個(gè)例子。比方說,這里有一個(gè)叫做 StuffClient 的服務(wù),它能夠勝任一些工作,同時(shí)還具有兩個(gè)配置選項(xiàng)(超時(shí)和重試)。

        type StuffClient interface {
            DoStuff() error
        }

        type stuffClient struct {
            conn    Connection
            timeout int
            retries int
        }

        這是個(gè)私有的結(jié)構(gòu)體,因此我們應(yīng)該為它提供某種構(gòu)造函數(shù):

        func NewStuffClient(conn Connection, timeout, retries int) StuffClient {
            return &stuffClient{
                conn:    conn,
                timeout: timeout,
                retries: retries,
            }
        }

        嗯,但是現(xiàn)在我們每次調(diào)用 NewStuffClient 函數(shù)時(shí)都要提供 timeoutretries。因?yàn)樵诖蠖鄶?shù)情況下,我們只想使用默認(rèn)值,我們無法使用不同參數(shù)數(shù)量帶定義多個(gè)版本的 NewStuffClient ,否則我們會(huì)得到一個(gè)類似 NewStuffClient redeclared in this block 編譯錯(cuò)誤。

        一個(gè)可選方案是創(chuàng)建另一個(gè)具有不同名稱的構(gòu)造函數(shù),例如:

        func NewStuffClient(conn Connection) StuffClient {
            return &stuffClient{
                conn:    conn,
                timeout: DEFAULT_TIMEOUT,
                retries: DEFAULT_RETRIES,
            }
        }
        func NewStuffClientWithOptions(conn Connection, timeout, retries int) StuffClient {
            return &stuffClient{
                conn:    conn,
                timeout: timeout,
                retries: retries,
            }
        }

        但是這么做的話有點(diǎn)蹩腳。我們可以做得更好,如果我們傳入了一個(gè)配置對(duì)象呢:

        type StuffClientOptions struct {
            Retries int //number of times to retry the request before giving up
            Timeout int //connection timeout in seconds
        }
        func NewStuffClient(conn Connection, options StuffClientOptions) StuffClient {
            return &stuffClient{
                conn:    conn,
                timeout: options.Timeout,
                retries: options.Retries,
            }
        }

        但是,這也不是很好的做法?,F(xiàn)在,我們總是需要?jiǎng)?chuàng)建 StuffClientOption 這個(gè)結(jié)構(gòu)體,即使不想在指定任何選項(xiàng)時(shí)還要傳遞它。另外我們也沒有自動(dòng)填充默認(rèn)值,除非我們?cè)诖a中的某處添加了一堆檢查,或者也可以傳入一個(gè) DefaultStuffClientOptions 變量(不過這么做也不好,因?yàn)樵谛薷哪骋惶幍胤胶罂赡軙?huì)導(dǎo)致其他的問題。)

        所以,更好的解決方法是什么呢?解決這個(gè)難題最好的解決方法是使用函數(shù)選項(xiàng)模式,它利用了 Go 對(duì)閉包更加方便的支持。讓我們保留上述定義的 StuffClientOptions ,不過我們?nèi)孕枰獮槠涮砑右恍﹥?nèi)容。

        type StuffClientOption func(*StuffClientOptions)
        type StuffClientOptions struct {
            Retries int //number of times to retry the request before giving up
            Timeout int //connection timeout in seconds
        }
        func WithRetries(r int) StuffClientOption {
            return func(o *StuffClientOptions) {
                o.Retries = r
            }
        }
        func WithTimeout(t int) StuffClientOption {
            return func(o *StuffClientOptions) {
                o.Timeout = t
            }
        }

        泥土般芬芳, 不是嗎?這到底是怎么回事?基本上,我們有一個(gè)結(jié)構(gòu)來定義 StuffClient 的可用選項(xiàng)。另外,現(xiàn)狀我們還定義了一個(gè)叫做 StuffClientOption 的東西(次數(shù)是單數(shù)),它只是接受我們選項(xiàng)的結(jié)構(gòu)體作為參數(shù)的函數(shù)。我們還定義了另外兩個(gè)函數(shù) WithRetriesWithTimeout ,它們返回一個(gè)閉包,現(xiàn)在就是見證奇跡的時(shí)刻了!

        var defaultStuffClientOptions = StuffClientOptions{
            Retries: 3,
            Timeout: 2,
        }
        func NewStuffClient(conn Connection, opts ...StuffClientOption) StuffClient {
            options := defaultStuffClientOptions
            for _, o := range opts {
                o(&options)
            }
            return &stuffClient{
                conn:    conn,
                timeout: options.Timeout,
                retries: options.Retries,
            }
        }

        現(xiàn)在,我們定義了一個(gè)額外和包含默認(rèn)選項(xiàng)的沒有導(dǎo)出的變量,同時(shí)我們已經(jīng)調(diào)整了構(gòu)造函數(shù),用來接收可變參數(shù)[1]。然后, 我們遍歷 StuffClientOption 列表(單數(shù)),針對(duì)每一個(gè)列表,將列表中返回的閉包使用在我們的 options 變量(需要記住,這些閉包接收一個(gè) StuffClientOptions 變量,僅需要在選項(xiàng)的值上做出少許修改)。

        現(xiàn)在我們要做的事情就是使用它!

        x := NewStuffClient(Connection{})
        fmt.Println(x) // prints &{{} 2 3}
        x = NewStuffClient(
            Connection{},
            WithRetries(1),
        )
        fmt.Println(x) // prints &{{} 2 1}
        x = NewStuffClient(
            Connection{},
            WithRetries(1),
            WithTimeout(1),
        )
        fmt.Println(x) // prints &{{} 1 1}

        這看起來相當(dāng)不錯(cuò),已經(jīng)可以使用了!而且,它的好處是,我們只需要對(duì)代碼進(jìn)行很少的修改,就可以隨時(shí)隨地添加新的選項(xiàng)。

        把這些修改放在一起,就是這樣:

        var defaultStuffClientOptions = StuffClientOptions{
            Retries: 3,
            Timeout: 2,
        }
        type StuffClientOption func(*StuffClientOptions)
        type StuffClientOptions struct {
            Retries int //number of times to retry the request before giving up
            Timeout int //connection timeout in seconds
        }
        func WithRetries(r int) StuffClientOption {
            return func(o *StuffClientOptions) {
                o.Retries = r
            }
        }
        func WithTimeout(t int) StuffClientOption {
            return func(o *StuffClientOptions) {
                o.Timeout = t
            }
        }
        type StuffClient interface {
            DoStuff() error
        }
        type stuffClient struct {
            conn    Connection
            timeout int
            retries int
        }
        type Connection struct {}
        func NewStuffClient(conn Connection, opts ...StuffClientOption) StuffClient {
            options := defaultStuffClientOptions
            for _, o := range opts {
                o(&options)
            }
                return &stuffClient{
                    conn:    conn,
                    timeout: options.Timeout,
                    retries: options.Retries,
                }
        }
        func (c stuffClient) DoStuff() error {
            return nil
        }

        如果你想自己嘗試一下,請(qǐng)?jiān)?Go Playground[2] 上查找。

        但這也可以通過刪除 StuffClientOptions 結(jié)構(gòu)體進(jìn)一步簡(jiǎn)化,并將選項(xiàng)直接應(yīng)用在我們的 StuffClient 上。

        var defaultStuffClient = stuffClient{
            retries: 3,
            timeout: 2,
        }
        type StuffClientOption func(*stuffClient)
        func WithRetries(r int) StuffClientOption {
            return func(o *stuffClient) {
                o.retries = r
            }
        }
        func WithTimeout(t int) StuffClientOption {
            return func(o *stuffClient) {
                o.timeout = t
            }
        }
        type StuffClient interface {
            DoStuff() error
        }
        type stuffClient struct {
            conn    Connection
            timeout int
            retries int
        }
        type Connection struct{}
        func NewStuffClient(conn Connection, opts ...StuffClientOption) StuffClient {
            client := defaultStuffClient
            for _, o := range opts {
                o(&client)
            }
            client.conn = conn
            return client
        }
        func (c stuffClient) DoStuff() error {
            return nil
        }

        這里[3]就能夠開始嘗試。在我們的示例中,我們只是將配置直接應(yīng)用于結(jié)構(gòu)體中,如果中間有一個(gè)額外的結(jié)構(gòu)體是沒有意義的。但是,請(qǐng)注意,在許多情況下,您可能仍然想使用上一個(gè)示例中的 config 結(jié)構(gòu)。例如,如果您的構(gòu)造函數(shù)正在使用 config 選項(xiàng)執(zhí)行某些操作時(shí),但是并沒有將它們存儲(chǔ)到結(jié)構(gòu)體中,或者被傳遞到其他地方,配置結(jié)構(gòu)的變體是更通用的實(shí)現(xiàn)。

        感謝 Rob Pike[4]Dave Cheney[5] 推廣這種設(shè)計(jì)模式。


        via: https://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/

        作者:ynori7[6]譯者:sunlingbot[7]校對(duì):unknwon[8]

        本文由 GCTT[9] 原創(chuàng)編譯,Go 中文網(wǎng)[10] 榮譽(yù)推出

        參考資料

        [1]

        可變參數(shù): https://gobyexample.com/variadic-functions

        [2]

        Go Playground: https://play.golang.org/p/VcWqWcAEyz

        [3]

        這里: https://play.golang.org/p/Z5P5Om4KDL

        [4]

        Rob Pike: https://commandcenter.blogspot.de/2014/01/self-referential-functions-and-design.html

        [5]

        Dave Cheney: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

        [6]

        ynori7: https://halls-of-valhalla.org/beta/user/ynori7

        [7]

        sunlingbot: https://github.com/sunlingbot

        [8]

        unknwon: https://github.com/unknwon

        [9]

        GCTT: https://github.com/studygolang/GCTT

        [10]

        Go 中文網(wǎng): https://studygolang.com/



        推薦閱讀


        福利

        我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        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>
            丝袜高跟国产成人精品一区 | 国产在线一区二区 | chinese国产打屁股实践视频下载 | 5x社区满18击进入av网站 | 男女插逼网站 | 黄片小网站 | 韩国成人网站 | 国产精品三区四区 | 成人久久精品人妻一区二区三区 | 18禁亚洲 |