自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

[[437104]]

大家好,我是 polarisxu。

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

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

Go 語(yǔ)言沒有構(gòu)造函數(shù),一般通過定義 New 函數(shù)來充當(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)體的模式,它通過設(shè)計(jì)一組非常有表現(xiàn)力和靈活的 API 來幫助配置和初始化結(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è)示例

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

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

  1. package main 
  2.  
  3. type Server { 
  4.   host string 
  5.   port int 
  6.  
  7. func New(host string, port int) *Server { 
  8.   return &Server{host, port} 
  9.  
  10. func (s *Server) Start() error { 

如何使用呢?

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.   "server" 
  6.  
  7. func main() { 
  8.   svr := New("localhost", 1234) 
  9.   if err := svr.Start(); err != nil { 
  10.     log.Fatal(err) 
  11.   } 

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

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

定義一個(gè)新的 Config 結(jié)構(gòu)體來保存配置信息

使用 Functional Option Pattern

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

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

  1. type Server { 
  2.  
  3. host string 
  4.  
  5. port int 
  6.  
  7. timeout time.Duration 
  8.  
  9. maxConn int 
  10.  

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

  1. func New(host string, port int) *Server { 
  2.  
  3. return &Server{host, port, time.Minute, 100} 
  4.  

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

  1. func NewWithTimeout(host string, port int, timeout time.Duration) *Server { 
  2.  
  3. return &Server{host, port, timeout} 
  4.  
  5.  
  6. func NewWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Server { 
  7.  
  8. return &Server{host, port, timeout, maxConn} 
  9.  

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

  1. func Dial(network, address string) (Conn, error) 
  2.  
  3. func DialTimeout(network, address string, timeout time.Duration) (Conn, error) 

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

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

  1. type Server { 
  2.   cfg Config 
  3.  
  4. type Config struct { 
  5.   Host string 
  6.   Port int 
  7.   Timeout time.Duration 
  8.   MaxConn int 
  9.  
  10. func New(cfg Config) *Server { 
  11.   return &Server{cfg} 

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

做法 3:使用 Functional Option Pattern

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

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

  1. type Option func(*Server) 

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

  1. func New(options ...Option) *Server { 
  2.  
  3. svr := &Server{} 
  4.  
  5. for _, f := range options { 
  6.  
  7. f(svr) 
  8.  
  9.  
  10. return svr 
  11.  

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

  1. func WithHost(host string) Option { 
  2.  
  3. return func(s *Server) { 
  4.  
  5. s.host = host 
  6.  
  7.  
  8.  
  9. func WithPort(port intOption { 
  10.  
  11. return func(s *Server) { 
  12.  
  13. s.port = port 
  14.  
  15.  
  16.  
  17. func WithTimeout(timeout time.Duration) Option { 
  18.  
  19. return func(s *Server) { 
  20.  
  21. s.timeout = timeout 
  22.  
  23.  
  24.  
  25. func WithMaxConn(maxConn intOption { 
  26.  
  27. return func(s *Server) { 
  28.  
  29. s.maxConn = maxConn 
  30.  
  31.  

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

  1. package main 
  2.  
  3. import ( 
  4.  
  5. "log" 
  6.  
  7. "server" 
  8.  
  9.  
  10. func main() { 
  11.  
  12. svr := New( 
  13.  
  14. WithHost("localhost"), 
  15.  
  16. WithPort(8080), 
  17.  
  18. WithTimeout(time.Minute), 
  19.  
  20. WithMaxConn(120), 
  21.  
  22.  
  23. if err := svr.Start(); err != nil { 
  24.  
  25. log.Fatal(err) 
  26.  
  27.  

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

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

  1. type Collector { 
  2.  
  3. // 省略... 
  4.  
  5.  
  6. func NewCollector(options ...CollectorOption) *Collector 
  7.  
  8. // 定義了一系列 CollectorOpiton 
  9.  
  10. type CollectorOption{ 
  11.  
  12. // 省略... 
  13.  
  14.  
  15. func AllowURLRevisit() CollectorOption 
  16.  
  17. func AllowedDomains(domains ...string) CollectorOption 
  18.  
  19. ... 

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

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

  1. type options struct { 
  2.   cache  bool 
  3.   logger *zap.Logger 
  4.  
  5. type Option interface { 
  6.   apply(*options) 
  7.  
  8. type cacheOption bool 
  9.  
  10. func (c cacheOption) apply(opts *options) { 
  11.   opts.cache = bool(c) 
  12.  
  13. func WithCache(c bool) Option { 
  14.   return cacheOption(c) 
  15.  
  16. type loggerOption struct { 
  17.   Log *zap.Logger 
  18.  
  19. func (l loggerOption) apply(opts *options) { 
  20.   opts.logger = l.Log 
  21.  
  22. func WithLogger(log *zap.Logger) Option { 
  23.   return loggerOption{Log: log} 
  24.  
  25. // Open creates a connection
  26. func Open
  27.   addr string, 
  28.   opts ...Option
  29. ) (*Connection, error) { 
  30.   options := options{ 
  31.     cache:  defaultCache, 
  32.     logger: zap.NewNop(), 
  33.   } 
  34.  
  35.   for _, o := range opts { 
  36.     o.apply(&options) 
  37.   } 
  38.  
  39.   // ... 

03 總結(jié)

在實(shí)際項(xiàng)目中,當(dāng)你要處理的選項(xiàng)比較多,或者處理不同來源的選項(xiàng)(來自文件、來自環(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

責(zé)任編輯:武曉燕 來源: polarisxu
相關(guān)推薦

2022-11-06 23:17:23

Go語(yǔ)言項(xiàng)目

2010-07-15 17:58:31

Perl模式

2022-04-24 15:29:17

微服務(wù)go

2021-11-08 07:41:16

Go流水線編程

2023-05-04 08:47:31

命令模式抽象接口

2023-04-10 09:20:13

設(shè)計(jì)模式訪客模式

2021-07-12 10:24:36

Go裝飾器代碼

2022-02-21 08:15:15

Go項(xiàng)目語(yǔ)言

2023-05-15 08:51:46

解釋器模式定義

2014-04-25 10:13:00

Go語(yǔ)言并發(fā)模式

2012-06-15 11:27:55

ibmdw

2012-04-05 11:52:43

ibmdw

2021-06-29 08:54:23

設(shè)計(jì)模式代理模式遠(yuǎn)程代理

2012-08-30 14:12:49

IBMdW

2023-05-26 08:41:23

模式Go設(shè)計(jì)模式

2025-03-24 00:25:00

Go語(yǔ)言并發(fā)編程

2021-07-07 10:31:19

對(duì)象池模式解釋器模式設(shè)計(jì)模式

2010-07-16 09:24:59

Perl模式

2011-06-28 15:01:01

Qt PIMPL

2022-07-03 14:03:57

分布式Seata
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)