前言
之所以使用 Functional options
的契機,是因為用到 gRPC 的 New Server API,發現他是用 functional options
來讓使用者調整 Server 預設配置,這樣的作法不但兼具了擴充性和可用性,也能避免一些使用者誤用。而除了看 source code 來學習如何實作之外,也找起相關文章,進而發現原來早在 2014 年就有人發表過類似教學文,實在是太孤陋寡聞了~
![study-2019-02]({{ site.url }}/assets/images/functional-options.png)
趁著這次機會,把相關文章的重點整理出來,讓大家在寫類似 API 時,也能做個參考。
Self-Referential Functions
首先要提到的是由 Rob Pike
所整理出 self-referential functions
的文章,此 Pattern 方式可用於:
- 有效地處理繁多且複雜的 setting options
- 需要保留之前所設定的 option
以下為文章中的實作例子:
1
2// 1. 定義 Option Type. Foo 為一個 struct 內含我們需要調整的變數
3type option func(f *Foo) option
4
5// 2. 使用 closure 來定義一個可以調整特定變數的 function
6func Verbosity(v int) option {
7 return func(f *Foo) option {
8 // 取出之前設定值
9 previous := f.verbosity
10 f.verbosity = v
11 // 回傳包含之前設定值的 option
12 return Verbosity(previous)
13 }
14}
15
16// 3. 接下來建立一個 function 來套用這些 options
17func (f *Foo) Option(opts ...option) (previous option) {
18 for _, opt := range opts {
19 previous = opt(f)
20 }
21 return previous
22}
23
24// 4. Usage
25prevVerbosity := foo.Option(Verbosity(3))
26foo.DoSomeDebugging()
27foo.Option(prevVerbosity)
從例子中可以看到,此 Pattern 利用 Closure 特性來生成 option type ,透過這樣的方式,可以簡單地套用這些 options 來達到修改指定變數之目的,且也能回復到之前設定的值,當然如果你不想 return 先前的 option 也行。總而言之,這樣的設計可以使用於多數需要進行參數配置的場景。
Functional options for friendly APIs
而 Dave Cheney
則是在這文章中用 functional options
來說明類似概念,不過文章中有舉例出過往為了解決這類 setting options 所用的各種方法,並點出這些方法的優缺點和強調 Functional options
的優勢,好讓使用者能做個比較。
方法:
- 在 funcation 中引入所有可以調整的參數
1func NewServer(port int, addr string)
這樣的寫法很直覺,但是一旦需要很多項設定時就很難擴充。
- configuration struct
1type Configuration struct {
2 port int
3 addr string
4}
5
6func NewServer(config Configuration) {
7 //
8}
使用 configuration 應該是蠻常見的作法,這樣提高的擴充性,不過卻也增加一些風險,例如套用 default 行為時,需要判斷這些變數是否存在再套用。當然你也可以設定一個 function 然後 return default configuration 來讓使用者套用。
1NewServer(Configuration{}) // May cause error
2
3defaultConfig := default()
4NewServer(defaultConfig)
不過,還有沒有其他的作法呢?
Functional Options
Functional Options
和上面所提及的 Self-Referential Functions
作法相似,一樣是藉由多個 functions 來修改特定變數值。而為了套用 Default 行為,我們可以事先定義一個含有預設值的變數,然後再根據使用者所引入的 options 來修改此預設變數。
1// 1. create a configuration type
2type Configuration struct {
3 port int
4 addr string
5}
6
7// 2. Create a default config
8var defaultConfig = {
9 port: 8080,
10 addr: "localhost"
11}
12
13// Create a option type and some closure functions to return option
14// Same as `Self-Referential Functions`
15
16// 3. Use options with default config
17func NewServer(opts... Option) {
18 // use these option functions to modify your default configuration
19 for _, opt := range opts {
20 opt(&defaultConfig)
21 }
22}
以上的 code 也是目前 gRPC 所使用的配置方式 - gRPC NewServer function source code
1// gRPC Example
2s := grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{}))