Go?函數(shù)選項模式
熟悉 Python 開發(fā)的同學(xué)都知道,Python 有默認(rèn)參數(shù)的存在,使得我們在實例化一個對象的時候,可以根據(jù)需要來選擇性的覆蓋某些默認(rèn)參數(shù),以此來決定如何實例化對象。當(dāng)一個對象有多個默認(rèn)參數(shù)時,這個特性非常好用,能夠優(yōu)雅地簡化代碼。
而 Go 語言從語法上是不支持默認(rèn)參數(shù)的,所以為了實現(xiàn)既能通過默認(rèn)參數(shù)創(chuàng)建對象,又能通過傳遞自定義參數(shù)創(chuàng)建對象,我們就需要通過一些編程技巧來實現(xiàn)。對于這些程序開發(fā)中的常見問題,軟件行業(yè)的先行者們總結(jié)了許多解決常見場景編碼問題的最佳實踐,這些最佳實踐后來成為了我們所說的設(shè)計模式。其中選項模式在 Go 語言開發(fā)中會經(jīng)常用到。
核心思想
定義一個 Option 函數(shù)類型,接收目標(biāo)結(jié)構(gòu)體的指針
創(chuàng)建多個返回 Option 的配置函數(shù)(通常以 With 開頭)
在構(gòu)造函數(shù)中使用可變參數(shù)接收這些選項函數(shù)
通常我們有以下三種方法來實現(xiàn)通過默認(rèn)參數(shù)創(chuàng)建對象,以及通過傳遞自定義參數(shù)創(chuàng)建對象:
- 使用多個構(gòu)造函數(shù)
- 默認(rèn)參數(shù)選項
- 選項模式
通過多構(gòu)造函數(shù)實現(xiàn)
第一種方式是通過多構(gòu)造函數(shù)實現(xiàn),下面是一個簡單例子:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
func NewServer() *Server {
return &Server{
Addr: defaultAddr,
Port: defaultPort,
}
}
func NewServerWithOptions(addr string, port int) *Server {
return &Server{
Addr: addr,
Port: port,
}
}
func main() {
s1 := NewServer()
s2 := NewServerWithOptions("localhost", 8001)
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
}這里我們?yōu)?Server 結(jié)構(gòu)體實現(xiàn)了兩個構(gòu)造函數(shù):
- NewServer:無需傳遞參數(shù)即可直接返回 Server 對象
- NewServerWithOptions :需要傳遞 addr 和 port 兩個參數(shù)來構(gòu)造 Server 對象
如果通過默認(rèn)參數(shù)創(chuàng)建的對象即可滿足需求,不需要對 Server 進(jìn)行定制時,我們可以使用 NewServer 來生成對象(s1)。而如果需要對 Server 進(jìn)行定制時,我們則可以使用 NewServerWithOptions 來生成對象(s2)。
通過默認(rèn)參數(shù)選項實現(xiàn)
另外一種實現(xiàn)默認(rèn)參數(shù)的方案,是為要生成的結(jié)構(gòu)體對象定義一個選項結(jié)構(gòu)體,用來生成要創(chuàng)建對象的默認(rèn)參數(shù),代碼實現(xiàn)如下:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
type ServerOptions struct {
Addr string
Port int
}
func NewServerOptions() *ServerOptions {
return &ServerOptions{
Addr: defaultAddr,
Port: defaultPort,
}
}
func NewServerWithOptions(opts *ServerOptions) *Server {
return &Server{
Addr: opts.Addr,
Port: opts.Port,
}
}
func main() {
s1 := NewServerWithOptions(NewServerOptions())
s2 := NewServerWithOptions(&ServerOptions{
Addr: "localhost",
Port: 8001,
})
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
}我們?yōu)?Server 結(jié)構(gòu)體專門實現(xiàn)了一個 ServerOptions 用來生成默認(rèn)參數(shù),調(diào)用 NewServerOptions 函數(shù)即可獲得默認(rèn)參數(shù)配置,構(gòu)造函數(shù) NewServerWithOptions 接收一個 *ServerOptions 類型作為參數(shù)。所以我們可以通過以下兩種方式來完成功能:
- 直接將調(diào)用 NewServerOptions 函數(shù)的返回值傳遞給 NewServerWithOptions 來實現(xiàn)通過默認(rèn)參數(shù)生成對象(s1)
- 通過手動構(gòu)造 ServerOptions 配置來生成定制對象(s2)
通過選項模式實現(xiàn)
以上兩種方式雖然都能夠完成功能,但卻有以下缺點:
- 通過多構(gòu)造函數(shù)實現(xiàn)的方案需要我們在實例化對象時分別調(diào)用不同的構(gòu)造函數(shù),代碼封裝性不強(qiáng),會給調(diào)用者增加使用負(fù)擔(dān)。
- 通過默認(rèn)參數(shù)選項實現(xiàn)的方案需要我們預(yù)先構(gòu)造一個選項結(jié)構(gòu),當(dāng)使用默認(rèn)參數(shù)生成對象時代碼看起來比較冗余。
而選項模式可以讓我們更為優(yōu)雅地解決這個問題。代碼實現(xiàn)如下:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
type ServerOptions struct {
Addr string
Port int
}
type ServerOption interface {
apply(*ServerOptions)
}
type FuncServerOption struct {
f func(*ServerOptions)
}
func (fo FuncServerOption) apply(option *ServerOptions) {
fo.f(option)
}
func WithAddr(addr string) ServerOption {
return FuncServerOption{
f: func(options *ServerOptions) {
options.Addr = addr
},
}
}
func WithPort(port int) ServerOption {
return FuncServerOption{
f: func(options *ServerOptions) {
options.Port = port
},
}
}
func NewServer(opts ...ServerOption) *Server {
options := ServerOptions{
Addr: defaultAddr,
Port: defaultPort,
}
for _, opt := range opts {
opt.apply(&options)
}
return &Server{
Addr: options.Addr,
Port: options.Port,
}
}
func main() {
s1 := NewServer()
s2 := NewServer(WithAddr("localhost"), WithPort(8001))
s3 := NewServer(WithPort(8001))
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
fmt.Println(s3) // &{127.0.0.1 8001}
}乍一看我們的代碼復(fù)雜了很多,但其實調(diào)用構(gòu)造函數(shù)生成對象的代碼復(fù)雜度是沒有改變的,只是定義上的復(fù)雜。
我們定義了 ServerOptions 結(jié)構(gòu)體用來配置默認(rèn)參數(shù)。因為 Addr 和 Port 都有默認(rèn)參數(shù),所以 ServerOptions 的定義和 Server 定義是一樣的。但有一定復(fù)雜性的結(jié)構(gòu)體中可能會有些參數(shù)沒有默認(rèn)參數(shù),必須讓用戶來配置,這時 ServerOptions 的字段就會少一些,大家可以按需定義。
同時,我們還定義了一個 ServerOption 接口和實現(xiàn)了此接口的 FuncServerOption 結(jié)構(gòu)體,它們的作用是讓我們能夠通過 apply 方法為 ServerOptions 結(jié)構(gòu)體單獨配置某項參數(shù)。
我們可以分別為每個默認(rèn)參數(shù)都定義一個 WithXXX 函數(shù)用來配置參數(shù),如這里定義的 WithAddr 和 WithPort ,這樣用戶就可以通過調(diào)用 WithXXX 函數(shù)來定制需要覆蓋的默認(rèn)參數(shù)。
此時默認(rèn)參數(shù)定義在構(gòu)造函數(shù) NewServer 中,構(gòu)造函數(shù)的接收一個不定長參數(shù),類型為 ServerOption,在構(gòu)造函數(shù)內(nèi)部通過一個 for 循環(huán)調(diào)用每個傳進(jìn)來的 ServerOption 對象的 apply 方法,將用戶配置的參數(shù)依次賦值給構(gòu)造函數(shù)內(nèi)部的默認(rèn)參數(shù)對象 options 中,以此來替換默認(rèn)參數(shù),for 循環(huán)執(zhí)行完成后,得到的 options 對象將是最終配置,將其屬性依次賦值給 Server 即可生成新的對象。
總結(jié)
通過 s2 和 s3 的打印結(jié)果可以發(fā)現(xiàn),使用選項模式實現(xiàn)的構(gòu)造函數(shù)更加靈活,相較于前兩種實現(xiàn),選項模式中我們可以自由的更改其中任意一項或多項默認(rèn)配置。
雖然選項模式確實會多寫一些代碼,但多數(shù)情況下這都是值得的。比如 Google 的 gRPC 框架 Go 語言實現(xiàn)中創(chuàng)建 gRPC server 的構(gòu)造函數(shù) NewServer 就使用了選項模式,感興趣的同學(xué)可以看下其源碼的實現(xiàn)思想其實和這里的示例程序如出一轍。
以上就是我關(guān)于 Golang 選項模式的一點經(jīng)驗,希望今天的分享能夠給你帶來一些幫助。
推薦閱讀
到此這篇關(guān)于Go 函數(shù)選項模式的文章就介紹到這了,更多相關(guān)go 函數(shù)選項模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
細(xì)說Go語言中空結(jié)構(gòu)體的奇妙用途
Go語言中,我們可以定義空結(jié)構(gòu)體,即沒有任何成員變量的結(jié)構(gòu)體,使用關(guān)鍵字?struct{}?來表示。這種結(jié)構(gòu)體似乎沒有任何用處,但實際上它在?Go?語言中的應(yīng)用非常廣泛,本文就來詳解講講2023-05-05
Go?Excelize?API源碼解析GetSheetFormatPr使用示例
這篇文章主要為大家介紹了Go?Excelize?API源碼解析GetSheetFormatPr使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
golang 函數(shù)以及函數(shù)和方法的詳解及區(qū)別
這篇文章主要介紹了golang 函數(shù)以及函數(shù)和方法的區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-05-05
Golang中interface轉(zhuǎn)string輸出打印方法
這篇文章主要給大家介紹了關(guān)于Golang中interface轉(zhuǎn)string輸出打印的相關(guān)資料,在go語言中interface轉(zhuǎn)string可以直接使用fmt提供的fmt函數(shù),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02

