概要
以下、自分用のメモです。そんなに使う場面は無いのですが、たまに使うときがあるので、ついでにここにメモメモ。。
Goには Functional Option Pattern という実装パターンがあって、Goにはオプショナルなパラメータが存在しないので擬似的に実現する方法ですね。
これが不便と思うことはそんなに無いのですが、たまーに必要になったりします。
考え方としては、パラメータとして「値を設定する関数」をもらうようにしておいて、生成時にそれをループさせて自身に適用させるという形です。
こんな感じ。
package main import "fmt" type ( Me struct { V1 int V2 string V3 int V4 string } ) func New(v1 int, v2 string, opts ...func(me *Me)) *Me { me := new(Me) me.V1 = v1 me.V2 = v2 me.V3 = -1 me.V4 = "unknown" for _, opt := range opts { opt(me) } return me } func main() { m1 := New(1, "hello") fmt.Printf("%v\n", m1) m2 := New(2, "world", func(me *Me) { me.V4 = "golang" }) fmt.Printf("%v\n", m2) }
$ go run main.go &{1 hello -1 unknown} &{2 world -1 golang}
でも、これだけだと利用者側に毎回 func(me *Me) 作ってもらわないと駄目だし、間違える可能性も高いので予め各値を設定する関数を返す関数を定義しておいて、それを使ってもらいましょうってなります。
こんな感じ。
package main import "fmt" type ( Me struct { V1 int V2 string V3 int V4 string } Option func(me *Me) ) func WithV3(v int) Option { return func(me *Me) { me.V3 = v } } func WithV4(v string) Option { return func(me *Me) { me.V4 = v } } func New(v1 int, v2 string, opts ...Option) *Me { me := new(Me) me.V1 = v1 me.V2 = v2 me.V3 = -1 me.V4 = "unknown" for _, opt := range opts { opt(me) } return me } func main() { m1 := New(1, "hello") fmt.Printf("%v\n", m1) m2 := New(2, "world", WithV4("golang")) fmt.Printf("%v\n", m2) }
同じことをしていますが、Withなんちゃらってものが存在しているので、利用者側からはこれを使って好きな設定を渡してもらうことが出来ます。
少しフレンドリーになった感じ。公開する側からみると、手間が増えますが。
サンプル(1) シンプルな形
config.go
package config import ( "fmt" "time" ) type ( Config struct { Addr string Port int RecvTimeout time.Duration SendTimeout time.Duration } // Option Pattern を実現するために用意 Option func(c *Config) ) func New(addr string, port int, options ...Option) *Config { c := new(Config) c.Addr = addr c.Port = port // Option Pattern for _, opt := range options { opt(c) } return c } func (c *Config) String() string { return fmt.Sprintf( "addr=%v:%v\trecvTimeout=%v\tsendTimeout=%v", c.Addr, c.Port, c.RecvTimeout, c.SendTimeout) } // Option Pattern func WithRecvTimeout(v time.Duration) Option { return func(c *Config) { c.RecvTimeout = v } } // Option Pattern func WithSendTimeout(v time.Duration) Option { return func(c *Config) { c.SendTimeout = v } }
main.go
// Option Pattern についてのサンプルです。 // // #REFERENCES // - https://dev.to/c4r4x35/options-pattern-in-golang-10ph package main import ( "fmt" "time" "github.com/devlights/try-golang/examples/singleapp/option_pattern/config" ) func main() { err := run() if err != nil { panic(err) } } func run() error { var ( c *config.Config ) c = config.New( "172.16.0.111", 8888, config.WithRecvTimeout(30*time.Second), config.WithSendTimeout(5*time.Second), ) fmt.Println(c) c = config.New( "localhost", 12345, ) fmt.Println(c) return nil }
$ task -d examples/singleapp/option_pattern/ task: [default] go run main.go addr=172.16.0.111:8888 recvTimeout=30s sendTimeout=5s addr=localhost:12345 recvTimeout=0s sendTimeout=0s
サンプル(2) 非公開な型を使ってのやり方
Option型と非公開なoptions型を定義して、利用者側にOptionを設定するためには WithXXX を指定しないと無理なようにするやり方。
package main import ( "errors" "fmt" "time" ) type ( Server struct { Addr string SendTimeout time.Duration } options struct { sendTimeout time.Duration } Option func(o *options) error ) func NewServer(addr string, opts ...Option) (*Server, error) { var options options for _, opt := range opts { err := opt(&options) if err != nil { return nil, err } } s := new(Server) s.Addr = addr s.SendTimeout = options.sendTimeout return s, nil } func WithSendTimeout(v time.Duration) Option { return func(o *options) error { if v < 1*time.Second { return errors.New("value should be greater than 1") } o.sendTimeout = v return nil } } func main() { s1, err := NewServer(":8888") if err != nil { fmt.Println(err) } fmt.Printf("%+v\n", s1) s2, err := NewServer(":8888", WithSendTimeout(0*time.Second)) if err != nil { fmt.Println(err) } fmt.Printf("%+v\n", s2) s3, err := NewServer(":8888", WithSendTimeout(3*time.Second)) if err != nil { fmt.Println(err) } fmt.Printf("%+v\n", s3) }
参考情報
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。