真實世界的 Go 設計模式 - 建造者模式
中文翻譯成 建造者模式、生成器模式。
一個遵循《設計模式》一書臆造出來的例子如:Builder in Go / Design Patterns (refactoring.guru)[1],複雜又難以理解。
實現strings.Builder
的關鍵點是使用了可變長度的緩衝區來存儲字符串,並在構建過程中動態地增加其大小,以適應不斷增長的字符串。下面是一個簡單的示例:
package main
import (
"fmt"
"strings"
)
func main() {
builder := strings.Builder{}
// 添加字符串片段到構建器
builder.WriteString("Hello, ")
builder.WriteString("World!")
// 獲取構建好的字符串
result := builder.String()
fmt.Println(result) // 輸出: Hello, World!
}
在這個例子中,我們首先使用strings.Builder{}
創建了一個新的構建器。然後,通過調用WriteString()
方法,我們可以向構建器添加字符串片段。最後,通過調用String()
方法,我們可以獲得構建好的最終字符串。
這裏值得注意的是,strings.Builder
在內部使用了一個可變長度的[]byte
緩衝區,它會根據需要自動增長。這樣,在構建字符串時,strings.Builder
會根據當前緩衝區的大小和新添加的字符串片段的長度來決定是否需要擴展緩衝區的大小,從而避免了頻繁的內存分配和拷貝操作。
這種實現方式在構建大型字符串時非常高效,因爲它最小化了內存分配和拷貝的開銷,同時還能提供簡潔而靈活的 API 來構建字符串。這是 Go 標準庫中使用 Builder 設計模式的一個很好的例子。
在地道的 Go 語言中,很少看到真正明顯的使用 Builder 進行構建對象的例子。第一,Go 一般直接 New 一個對象,傳入相應的值進行字段的設置,相當的簡潔粗暴,第二,Go 的風格是很少使用方法鏈式調用 (Method Chaining), 而是使用功能選項風格 (functional option)。鏈式調用是很好實現 Builder 的一種方式。
比如下面這個例子,我們對*http.Request
進行包裝,生成一個新的支持鏈式構建的Request
。在創建一個新的Request
的時候,我們使用鏈式調用進行初始化,增加了Cookie
和查詢參數:
package chaining
import (
"fmt"
"net/http"
)
type Request struct {
*http.Request
}
func main() {
// 使用鏈式調用創建一個GET請求
request, _ := http.NewRequest("GET", "https://www.example.com", nil)
req := &Request{request}
req.WithHeader("User-Agent", "MyCustomUserAgent").
WithCookie(&http.Cookie{Name: "session", Value: "abc123"}).
WithQueryParam("key", "value")
// 打印構建好的請求
fmt.Println(request)
}
// WithHeader 添加請求頭
func (r *Request) WithHeader(key, value string) *Request {
r.Header.Add(key, value)
return r
}
// WithCookie 添加Cookie
func (r *Request) WithCookie(cookie *http.Cookie) *Request {
r.AddCookie(cookie)
return r
}
// WithQueryParam 添加URL查詢參數
func (r *Request) WithQueryParam(key, value string) *Request {
query := r.URL.Query()
query.Add(key, value)
r.URL.RawQuery = query.Encode()
return r
}
但是,這種方式在 Go 生態圈中還是比較少用的,比如*http.Request
還是通過逐步調用進行一步步的構建。
現在在 Go 生態圈流行一種叫 functional option 的創建模式。這個想法最早來自於 Rob Pike 的 [Self-referential functions and the design of options](command center: Self-referential functions and the design of options[2]),後來不知道怎麼被大佬們介紹就流行起來了。Go 一般創建對象會使用以下幾個方式:
-
New(x int, y stirng, z S):選項太多時參數太長。
-
NewXXX(...)、NewYYY(...)、NewZZZ(...): 爲每個配置選項聲明一個新的構造函數,多個選項同時支持時方法太多。
-
New(config Config): 定義一個配置對象,這是在很多選項的情況很多 gopher 採用的一種方式
-
New(options ...func(*Server)):使用功能選項 WithHost、WithPort、WithTimeout 等作爲 opton
-
鏈式構建:如上面的例子,Go 生態圈中少用
我看到功能選項有被濫用的趨勢,也有人建議我開發的 rpcx 框架中使用功能選項模式,我拒絕了,因爲簡單的一個字段的賦值就使用功能選項,我個人認爲有點大材小用了,但是我也沒有大張旗鼓的去寫文章去批判它,我怕會被它的擁躉羣起攻之。選擇自己喜歡的就好。所以 etcd 的作者就同時使用了兩種方式,蘿蔔白菜,各取所愛:
func New(cfg Config) (*Client, error)
func NewCtxClient(ctx context.Context, opts ...Option) *Client
參考資料
[1]
Builder in Go / Design Patterns (refactoring.guru): https://refactoring.guru/design-patterns/builder/go/example
[2]
Self-referential functions and the design of options: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/jXaCLkagT-cKbVHConcadg