フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

Go の Option パターンを generics と reflect でシンプルにしてみる

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 で一意にし突っ込んでいます。