作為golang開發(fā)人員,您將遇到的許多問題之一是嘗試將函數(shù)的參數(shù)設(shè)置為可選。有時候使用默認(rèn)設(shè)置,但有時候需要提供自定義設(shè)置。
創(chuàng)新互聯(lián)主要從事成都網(wǎng)站制作、做網(wǎng)站、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)井陘,10年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575
在許多語言中,這很容易;在c系列語言中,您可以使用不同數(shù)量的參數(shù)提供相同函數(shù)的多個版本,在php這樣的語言中,您可以為參數(shù)提供默認(rèn)值,并在調(diào)用方法時忽略它們。但在golang您不能做到這兩點。那么在go中該如何實現(xiàn)呢?
我們來看一個例子吧,假設(shè)我們有一個名為StuffClient的服務(wù),它可以執(zhí)行一些操作并具有兩個配置選項(超時和重試):
type StuffClient interface {
DoStuff() error
}
type stuffClient struct {
conn Connection
timeout int
retries int
}
該結(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ù)時候我們只想使用默認(rèn)值。我們無法使用不同數(shù)量的參數(shù)定義多個版本的NewStuffClient, 否則我們將得到一個編譯錯誤。
一種選擇時創(chuàng)建另一個具有不同名稱的構(gòu)造函數(shù),例如
func NewStuffClient (conn Connection) StuffClient {
return &stuffClient {
conn: conn,
timeout: DefaultTimeout,
retries: DefaultRetries,
}
}
func NewStuffClienWithOptions(conn Connection, timeout, retries int) StuffClient {
return &stuffClient {
conn: conn,
timeout: timeout,
retries: retries,
}
}
我們還可以做的更好,將所有選項放到配置對象中
type StuffClientOptions struct {
Retries int
Timeout int
}
func NewStuffClient(conn Connection, options StuffClientOptions) StuffClient {
return &stuffClient {
conn: conn,
timeout: options.Timeout,
retries: options.Retries,
}
}
但那也不是很好,現(xiàn)在我們必須這個結(jié)構(gòu)并傳入它,即使我們不想指定任何選項,我們也沒有自動填寫的默認(rèn)值,除非我們在代碼中添加了一堆檢查或者我們可以傳入一個DefaultSuffClientOptions變量(但這可能會導(dǎo)致在一個地方被修改,影響別的地方)
那么解決方案是什么?解決這個難題的最好方法就是使用函數(shù)選項模式,利用go閉包的方便支持,讓我們保留上面定義的 StuffClientOptions,但我們會添加一些東西:
type StuffClientOption func(*StuffClientOptions)
type StuffClientOptions struct {
Retries int
Timeout int
}
func WithRetries(r int) StuffClientOption {
return func(o *StuffClientOptions) {
o.retries = r
}
}
func WithTimeout(t int) StuffClientOption {
return func(o *StuffClientOptions) {
o.timeout = t
}
}
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)在看起來已經(jīng)非常好用了。關(guān)于它的好處是我們可以隨時添加新選項,只需要對代碼進(jìn)行少量的更改。
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
}
我們也可以通過刪除 StuffClientOptions 結(jié)構(gòu)并將選項直接應(yīng)用于我們的StuffClient, 可以進(jìn)一步簡化這一過程
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
}
在我們的示例中,只是將配置直接應(yīng)用于結(jié)構(gòu),在中間有一個額外的配置結(jié)構(gòu)是沒有意義的,但請注意,在許多情況下,您可能仍希望使用上一個示例中的config結(jié)構(gòu),例如:如果你的構(gòu)造函數(shù)使用配置選項來執(zhí)行某些操作但并沒有將它們存儲到結(jié)構(gòu)中,或者他們被傳遞到其他地方。config結(jié)構(gòu)變量是更通用的實現(xiàn)。
type options struct{
timeout time.Duration
}
var defaultOptions = options{}
type option func(*options)
func WithTimeout(t time.Duration) options {
return func(o *options) {
o.timeout = t
}
}
func Do(opts ...option) {
d := defaultOptions
for _, o := range opts {
o(&d)
}
}
https://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/
Dave Cheney: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
Rob Pike: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html