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

