<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 編程模式系列(三):FUNCTIONAL OPTIONS

          共 5541字,需瀏覽 12分鐘

           ·

          2021-01-09 20:40

          原文作者陳皓(左耳朵耗子)

          內(nèi)容出

          https://coolshell.cn/articles/21146.html


          在本篇文章中,我們來討論一下Functional Options這個編程模式。這是一個函數(shù)式編程的應(yīng)用案例,編程技巧也很好,是目前在Go語言中最流行的一種編程模式。但是,在我們正式討論這個模式之前,我們需要先來看看要解決什么樣的問題。

          本文是全系列中第3 / 9篇:Go編程模式

          • Go編程模式:切片,接口,時間和性能
          • Go 編程模式:錯誤處理
          • Go 編程模式:Functional Options
          • Go編程模式:委托和反轉(zhuǎn)控制
          • Go編程模式:Map-Reduce
          • Go 編程模式:Go Generation
          • Go編程模式:修飾器
          • Go編程模式:Pipeline
          • Go 編程模式:k8s Visitor 模式

          目錄

          • 配置選項問題
          • 配置對象方案
          • Builder模式
          • Functional Options

          配置選項問題


          在我們編程中,我們會經(jīng)常性的需要對一個對象(或是業(yè)務(wù)實體)進(jìn)行相關(guān)的配置。比如下面這個業(yè)務(wù)實體(注意,這僅只是一個示例):

          type Server struct {    Addr     string    Port     int    Protocol string    Timeout  time.Duration    MaxConns int    TLS      *tls.Config}

          在這個?Server?對象中,我們可以看到:

          • 要有偵聽的IP地址?Addr?和端口號?Port?,這兩個配置選項是必填的(當(dāng)然,IP地址和端口號都可以有默認(rèn)值,當(dāng)這里我們用于舉例認(rèn)為是沒有默認(rèn)值,而且不能為空,需要必填的)。
          • 然后,還有協(xié)議?Protocol?、?Timeout?和MaxConns?字段,這幾個字段是不能為空的,但是有默認(rèn)值的,比如:協(xié)議是tcp, 超時30秒 和 最大鏈接數(shù)1024個。
          • 還有一個?TLS?這個是安全鏈接,需要配置相關(guān)的證書和私鑰。這個是可以為空的。

          所以,針對于上述這樣的配置,我們需要有多種不同的創(chuàng)建不同配置?Server?的函數(shù)簽名,如下所示(代碼比較寬,需要左右滾動瀏覽):

          func NewDefaultServer(addr string, port int) (*Server, error) {  return &Server{addr, port, "tcp", 30 * time.Second, 100, nil}, nil}func NewTLSServer(addr string, port int, tls *tls.Config) (*Server, error) {  return &Server{addr, port, "tcp", 30 * time.Second, 100, tls}, nil}func NewServerWithTimeout(addr string, port int, timeout time.Duration) (*Server, error) {  return &Server{addr, port, "tcp", timeout, 100, nil}, nil}func NewTLSServerWithMaxConnAndTimeout(addr string, port int, maxconns int, timeout time.Duration, tls *tls.Config) (*Server, error) {  return &Server{addr, port, "tcp", 30 * time.Second, maxconns, tls}, nil}

          因為Go語言不支持重載函數(shù),所以,你得用不同的函數(shù)名來應(yīng)對不同的配置選項。


          配置對象方案


          要解決這個問題,最常見的方式是使用一個配置對象,如下所示:

          type Config struct {    Protocol string    Timeout  time.Duration    Maxconns int    TLS      *tls.Config}

          我們把那些非必輸?shù)倪x項都移到一個結(jié)構(gòu)體里,于是?Server?對象變成了:

          type Server struct {    Addr string    Port int    Conf *Config}

          于是,我們只需要一個?NewServer()?的函數(shù)了,在使用前需要構(gòu)造?Config?對象。

          func NewServer(addr string, port int, conf *Config) (*Server, error) {    //...}//Using the default configuratrionsrv1, _ := NewServer("localhost", 9000, nil) conf := ServerConfig{Protocol:"tcp", Timeout: 60*time.Duration}srv2, _ := NewServer("locahost", 9000, &conf)

          這段代碼算是不錯了,大多數(shù)情況下,我們可能就止步于此了。但是,對于有潔癖的有追求的程序員來說,他們能看到其中有一點不好的是,Config?并不是必需的,所以,你需要判斷是否是?nil?或是 Empty –?Config{}這讓我們的代碼感覺還是有點不是很干凈。


          Builder模式


          如果你是一個Java程序員,熟悉設(shè)計模式的一定會很自然地使用上Builder模式。比如如下的代碼:

          User user = new User.Builder()  .name("Hao Chen")  .email("haoel@hotmail.com")  .nickname("左耳朵")  .build();

          仿照上面這個模式,我們可以把上面代碼改寫成如下的代碼(注:下面的代碼沒有考慮出錯處理,其中關(guān)于出錯處理的更多內(nèi)容,請參看《Go 編程模式:出錯處理》):

          //使用一個builder類來做包裝type ServerBuilder struct {  Server}func (sb *ServerBuilder) Create(addr string, port int) *ServerBuilder {  sb.Server.Addr = addr  sb.Server.Port = port  //其它代碼設(shè)置其它成員的默認(rèn)值  return sb}func (sb *ServerBuilder) WithProtocol(protocol string) *ServerBuilder {  sb.Server.Protocol = protocol   return sb}func (sb *ServerBuilder) WithMaxConn( maxconn int) *ServerBuilder {  sb.Server.MaxConns = maxconn  return sb}func (sb *ServerBuilder) WithTimeOut( timeout time.Duration) *ServerBuilder {  sb.Server.Timeout = timeout  return sb}func (sb *ServerBuilder) WithTLS( tls *tls.Config) *ServerBuilder {  sb.Server.TLS = tls  return sb}func (sb *ServerBuilder) Build() (Server) {  return  sb.Server}

          于是就可以以如下的方式來使用了:

          sb := ServerBuilder{}server, err := sb.Create("127.0.0.1", 8080).  WithProtocol("udp").  WithMaxConn(1024).  WithTimeOut(30*time.Second).  Build()

          上面這樣的方式也很清楚,不需要額外的Config類,使用鏈?zhǔn)降暮瘮?shù)調(diào)用的方式來構(gòu)造一個對象,只需要多加一個Builder類,這個Builder類似乎有點多余,我們似乎可以直接在Server?上進(jìn)行這樣的 Builder 構(gòu)造,的確是這樣的。但是在處理錯誤的時候可能就有點麻煩(需要為Server結(jié)構(gòu)增加一個error 成員,破壞了Server結(jié)構(gòu)體的“純潔”),不如一個包裝類更好一些。

          如果我們想省掉這個包裝的結(jié)構(gòu)體,那么就輪到我們的Functional Options上場了,函數(shù)式編程。


          Functional Options


          首先,我們先定義一個函數(shù)類型:

          type Option func(*Server)

          然后,我們可以使用函數(shù)式的方式定義一組如下的函數(shù):

          func Protocol(p string) Option {    return func(s *Server) {        s.Protocol = p    }}func Timeout(timeout time.Duration) Option {    return func(s *Server) {        s.Timeout = timeout    }}func MaxConns(maxconns int) Option {    return func(s *Server) {        s.MaxConns = maxconns    }}func TLS(tls *tls.Config) Option {    return func(s *Server) {        s.TLS = tls    }}

          上面這組代碼傳入一個參數(shù),然后返回一個函數(shù),返回的這個函數(shù)會設(shè)置自己的?Server?參數(shù)。例如:

          • 當(dāng)我們調(diào)用其中的一個函數(shù)用?MaxConns(30)?時
          • 其返回值是一個?func(s* Server) { s.MaxConns = 30 }?的函數(shù)。

          這個叫高階函數(shù)。在數(shù)學(xué)上,就好像這樣的數(shù)學(xué)定義,計算長方形面積的公式為:?rect(width, height) = width * height;?這個函數(shù)需要兩個參數(shù),我們包裝一下,就可以變成計算正方形面積的公式:square(width) = rect(width, width)?也就是說,squre(width)返回了另外一個函數(shù),這個函數(shù)就是rect(w,h)?只不過他的兩個參數(shù)是一樣的。即:f(x)? = g(x, x)

          好了,現(xiàn)在我們再定一個?NewServer()的函數(shù),其中,有一個可變參數(shù)?options?其可以傳出多個上面上的函數(shù),然后使用一個for-loop來設(shè)置我們的?Server?對象。

          func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {  srv := Server{    Addr:     addr,    Port:     port,    Protocol: "tcp",    Timeout:  30 * time.Second,    MaxConns: 1000,    TLS:      nil,  }  for _, option := range options {    option(&srv)  }  //...  return &srv, nil}

          于是,我們在創(chuàng)建?Server?對象的時候,我們就可以這樣來了。

          s1, _ := NewServer("localhost", 1024)s2, _ := NewServer("localhost", 2048, Protocol("udp"))s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))

          怎么樣,是不是高度的整潔和優(yōu)雅?不但解決了使用?Config?對象方式 的需要有一個config參數(shù),但在不需要的時候,是放?nil?還是放?Config{}的選擇困難,也不需要引用一個Builder的控制對象,直接使用函數(shù)式編程的試,在代碼閱讀上也很優(yōu)雅。

          所以,以后,大家在要玩類似的代碼時,強(qiáng)烈推薦使用Functional Options這種方式,這種方式至少帶來了如下的好處:

          • 直覺式的編程
          • 高度的可配置化
          • 很容易維護(hù)和擴(kuò)展
          • 自文檔
          • 對于新來的人很容易上手
          • 沒有什么令人困惑的事(是nil 還是空)

          參考文檔


          • “Self referential functions and design” by Rob Pike
            http://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html

          (全文完)



          瀏覽 85
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  亚洲人无码网 | 俺去也一二三区 | 亚洲内射视频 | 免费黄色电影中文字幕 | 99毛片 |