Go語言實踐模式 – 函數選項模式(Functional Options Pattern)
什麼是函數選項模式
大家好,我是小白,有點黑的那個白。
最近遇到一個問題,因為業務需求,需要對接三方平台.
而三方平台提供的一些HTTP(S)介面都有統一的密鑰生成規則要求.
為此我們封裝了一個獨立的包 xxx-go-sdk 以便維護和對接使用.
其中核心的部分是自定義HTTP Client,如下:
type Client struct {}
func (c *Client) do() {
// 實現統一的加密和簽名邏輯
// 統一調用net/http
}
// 訂單列表介面
func (c *Client) OrderList(){
c.do()
}
// 訂單發貨介面
func (c *Client) OrderDelivery(){
c.do()
}
// ... 其他介面
一些平台會要求appKey/appSecret等資訊,所以Client結構體就變成了這樣,這時參數還比較少, 而且是必填的參數,我們可以提供構造函數來明確指定。
type Client struct {
AppKey string
AppSecret string
}
func NewClient(appKey string, appSecret string) *Client {
c := new(Client)
c.AppKey = appKey
c.AppSecret = appSecret
return c
}
看起來很滿足,但是當我們需要增加一個 Timeout 參數來控制超時呢?
或許你會說這還不簡單,像下面一樣再加一個參數唄
type Client struct {
AppKey string
AppSecret string
Timeout time.Duration
}
func NewClient(appKey string, appSecret string, timeout time.Duration) *Client {
c := new(Client)
c.AppKey = appKey
c.AppSecret = appSecret
c.Timeout = timeout
return c
}
那再加些其他的參數呢?那構造函數的參數是不是又長又串,而且每個參數不一定是必須的,有些參數我們有會考慮默認值的問題。
為此,勤勞但尚未致富的 gophers 們使用了總結一種實踐模式
首先提取所有需要的參數到一個獨立的結構體 Options,當然你也可以用 Configs 啥的.
type Options struct {
AppKey string
AppSecret string
}
然後為每個參數提供設置函數
func WithAppKey(appKey string) func(*Options) {
return func(o *Options) {
o.AppKey = appKey
}
}
func WithAppSecret(appSecret string) func(*Options) {
return func(o *Options) {
o.AppSecret = appSecret
}
}
這樣我們就為每個參數設置了獨立的設置函數。返回值 func(*Options)
看著有點不友好,我們提取下定義為單個 Option
調整一下程式碼
type Option func(*Options)
func WithAppKey(appKey string) Option {
return func(o *Options) {
o.AppKey = appKey
}
}
func WithAppSecret(appSecret string) Option {
return func(o *Options) {
o.AppSecret = appSecret
}
}
當我們需要添加更多的參數時,只需要在 Options 添加新的參數並添加新參數的設置函數即可。
比如現在要添加新的參數 Timeout
type Options struct {
AppKey string
AppSecret string
Timeout. time.Duration // 新增參數
}
// Timeout 的設置函數
func WithTimeout(timeout time.Duration) Option {
return func(o *Options) {
o.Timeout = timeout
}
}
這樣後續不管新增多少參數,只需要新增配置項並添加獨立的設置函數即可輕鬆擴展,並且不會影響原有函數的參數順序和個數位置等。
至此,每個選項是區分開來了,那麼怎麼作用到我們的 Client 結構體上呢?
首先,配置選項都被提取到了 Options 結構體中,所以我們需要調整一下 Client 結構體的參數
type Client struct {
options *Options
}
其次,每一個選項函數返回 Option,那麼任意多個就是 …Option,我們調整一下構造函數 NewClient 的參數形式,改為可變參數,不在局限於固定順序的幾個參數。
func NewClient(options ...Option) *Client {
c := new(Client)
c.Options = ?
return c
}
然後循環遍歷每個選項函數,來生成Client結構體的完整配置選項。
func NewClient(options ...Option) *Client {
opts := new(Options)
for _, o := range options {
o(opts)
}
c := new(Client)
c.Options = opts
return c
}
那麼怎麼調用呢?對於調用方而已,直接在調用構造函數NewClient()的參數內添加自己需要的設置函數(WithXXX)即可
client := NewClient(
WithAppKey("your-app-key"),
WithAppSecret("your-app-secret"),
)
當需要設置超時參數,直接添加 WithTimeout即可,比如設置3秒的超時
client := NewClient(
WithAppKey("your-app-key"),
WithAppSecret("your-app-secret"),
WithTimeout(3*time.Second),
)
配置選項的位置可以任意設置,不需要受常規的固定參數順序約束。
可以看到,這種實踐模式主要作用於配置選項,利用函數支援的特性來實現的,為此得名 Functional Options Pattern,優美的中國話叫做「函數選項模式」。
總結
最後, 我們總結回顧一下在Go語言中函數選項模式的優缺點
優點
- 支援多參數;
- 支援參數任意位置順序;
- 支援默認值設置;
- 向後兼容,擴展性極佳;
- 用戶使用行為一致, 體感良好.
缺點
這是特性,不是缺點 – -!
- 增加了Options結構和Option定義;
- 針對每個參數都有對應的設置函數,每個選項函數的實現程式碼量好像多了一些;