GoのFunctional Options Patternについて
はじめに
Go には言語仕様としてのコンストラクタの機能がありません.そのため,Go で構造体の初期化などを行う際には関数名の最初にNew
をつけた関数をコンストラクタの代わりに使用する場合が多いです.
その際に同時に使用されることが多いFunctional Options Patternについてまとめた記事です.Functional Options Patternってなんだろうという人の参考になれば幸いです.
Functional Options Pattern
type Server struct {
Addr string
Port int
Timeout time.Duration
}
func DefaultServer() *Server {
return &Server {
Addr : "localhost",
Port : 8080,
Timeout : 10*time.Second,
}
}
type Option func(*Server)
func NewServer(opts ...Option) *Server {
s := DefaultServer()
for _, f := range opts {
f(s)
}
return s
}
type Config struct {
Timeout time.Duration
Proxy string
}
type Option func(*Config)
func WithTimeout(d time.Duration) Option {
return func(c *Config) {
c.Timeout = d
}
}
func WithProxy(u string) Option {
return func(c *Config) {
c.Proxy = u
}
}
func SendRequest(u string, opts ...Option) error { /* do something */ }
Functional Options Patternは Go で構造体を初期化したり,任意のオプションを関数に渡すために使用されるパターンで上記のように任意の構造体のポインタを受け取り,受け取った構造体のフィールドを書き換えるような処理を行います.
この方法による初期化のメリットとして,以下のようなものが挙げられます
1. 拡張性が高い
まず,Functional Options パターンは設定項目を可変長配列として受け取るため新たなオプションを追加したときに引数を変更する必要がありません.
そのため,拡張性が関数の引数としてオプションを受け取るときよりも良いです.
2. 任意引数を用意することができる
Python などに有るような,引数の値が指定されなければデフォルト値が使用されるような挙動を行う関数を作成する事ができます.例えば,上記のserver.go
では,DefaultServer
関数で規定のServer
構造体を作成しています.
Option
が指定されなかったフィールドはDefaultServer
で指定された値が引き継がれるため変更したいフィールドに対応した関数のみを渡せば良くなります.このため,擬似的に引数が既定値を持ったような関数を作成することが可能となります.
また,request.go
のように必須引数のみ引数として受け取るようにしそれ以外の任意引数は可変長配列として受け取るような実装を行うことで必須引数と任意引数を Go で使用することが出来ます.
最後に
Go でプログラムを書いていくうえで Functional Options Pattern が使えると作成する関数の表現力が広がるため,知らなかったという人はこれをきっかけに使用してみてください.
Discussion