Goではインスタンスを生成する New に対していろんなパターンで作成をするために、よく Option パターンと呼ばれる書き方をします。
こんな感じです。(WithXXX
で設定したい内容を付与できる)
package main import ( "fmt" ) func main() { p := NewProduct( WithName("iPhone"), WithId(1), WithParam("isLatest", true)) fmt.Println(p) } type Option func(*product) type product struct { name string id int isLatest bool } func NewProduct(opts ...Option) *product { p := &product{} for _, opt := range opts { opt(p) } return p } func WithName(name string) Option { return func(p *product) { p.name = name } } func WithId(id int) Option { return func(p *product) { p.id = id } } func WithLatest(isLatest bool) Option { return func(p *product) { p.isLatest = isLatest } }
これのメリットは基本的な値は引数で渡しつつも、Optional で設定したいものだけ必要に応じて設定できるところです。 非常に便利なのですが一個問題があってオプションの種類が増えてくるとズラズラと書く羽目になるので面倒です。
Generics を使ってシンプルに
Go 1.19 で登場した Generics を使ってシンプルに書いてみようと思います。 それがこちら。
package main import ( "fmt" "reflect" "unsafe" ) func main() { p = NewProduct( WithParam("name", "Android"), WithParam("id", 2), WithParam("isLatest", true)) fmt.Println(p) } type Option func(*product) type product struct { name string id int isLatest bool } func NewProduct(opts ...Option) *product { p := &product{} for _, opt := range opts { opt(p) } return p } type Param interface { int | string | bool } func WithParam[T Param](param string, value T) Option { return func(p *product) { // p のフィールドを取得 v := reflect.ValueOf(p).Elem().FieldByName(param) // フィールドのポインタを取得し、unsafe.Pointer に変換後、*T に変換 pointer := (*T)(unsafe.Pointer(v.UnsafeAddr())) // *T に値を代入 *pointer = value } }
WithParam
一つで全てを賄っています。
コメントにも書いていますが、reflect
を使って product インスタンスのポインタを取得し、そこから任意のパラメータのポインタを取得しています。
その後その型を generics で一意にし突っ込んでいます。