【Go言語】個人的に1番良さげなDecoratorパターンの実装


はじめに

Golangで実装するDecoratorパターンについて勉強した際、参考資料に載せてあるように多くの方法がありました。

どんな実装が1番良いか模索した結果、個人的に↓で書いたコードが一番読みやすくて良いかなと思いました。

コード

今回実装したコードについて説明します。
Decoratorパターンを使ってデフォルトのアバターをカスタマイズし、装備を着けて強化していくプログラムになっています。

仮にDecoratorパターンを使わない場合、最初から装飾されたオブジェクトを用意しておいて生成する方法になると思いますが、
装備が増えた時に全パターン分用意することになるので、コード数がかなり多くなってしまいます。
なら装備を部品として用意してアバターに柔軟に着けていくのが一番良さそうです。

main.go
type Avatar struct {
    Name       string
    HP         int
    WeaponList []string
}

type CustomizerFunc func(*Avatar) *Avatar

func Custom(i *Avatar, customizerFuncs ...CustomizerFunc) {
    for _, customizerFunc := range customizerFuncs {
        i = customizerFunc(i)
    }
}

func Attach(weapon string, hp int) CustomizerFunc {
    return CustomizerFunc(func(i *Avatar) *Avatar {
        i.WeaponList = append(i.WeaponList, weapon)
        i.HP += hp
        return i
    })
}

func main() {
    // default Avatar
    avatar := &Avatar{Name: "久川善法", HP: 100}
    fmt.Printf("カスタマイズ前:%+v\n", avatar)

    // custom Avatar
    Custom(avatar, Attach("gun", 1), Attach("helmet", 10))
    fmt.Printf("カスタマイズ後:%+v", avatar)
}

// カスタマイズ前:&{Name:久川善法 HP:100 WeaponList:[]}
// カスタマイズ後:&{Name:久川善法 HP:111 WeaponList:[gun helmet]}

コードの解説

・型定義

予約語であるtypeを使って、既存の型や型リテラルに別名をつけることができます。
関数型のCustomizerFunc型を定義しました。
引数でAvatarを受け取りAvatarを返す関数です。

type CustomizerFunc func(*Avatar) *Avatar

・デコレータパターンの実装

上記で定義したCustomizerFunc型を複数受け取り、順番にその関数を実行しています。
i = customizerFunc(i)とあるので、Avatarを受け取ってAvatarを返しています。

func Custom(i *Avatar, customizerFuncs ...CustomizerFunc) {
    for _, customizerFunc := range customizerFuncs {
        i = customizerFunc(i)
    }
}

※余談
i = customizerFunc(i)
この実装がわかりにくいかもしれないので、他の視点からも。

func main() {
    // 無名関数を定義しています。
    function := func(str string) string {
        return "*" + str + "*"
    }
    // 引数を渡して無名関数を実行しています。
    f := function("golang")
    fmt.Println(f)
}
// *golang*

・部品の用意

返り値はCustomizerFuncなので、return CustomizerFunc()を返しています。
CustomizerFunc型func(*Avatar) *Avatarのエイリアスなので、functionを書きます。
そして引数で受け取ったAvatarのHPやWeaponListに値を追加して返却しています。

func Attach(weapon string, hp int) CustomizerFunc {
    return CustomizerFunc(func(i *Avatar) *Avatar {
        i.WeaponList = append(i.WeaponList, weapon)
        i.HP += hp
        return i
    })
}

・余談
個人的にreturn CustomizerFunc()がわかりにくかったです。
簡単な例で同じ実装をすると以下のようになります。

type Number uint

func main() {
    num := Number(11)
    fmt.Println(num)
}

・最後にDecoratorを呼び出す

最後にavatarを第一引数に渡し、第二引数以降のAttachで装飾しています。

Custom(avatar, Attach("gun", 1), Attach("helmet", 10))

最後に

Function型のエイリアスを定義することがあまりないので、最初はコードを読むのに苦労しました。
簡単な例に置き換えてみるととてもわかりやすかったです。
個人的に実装してきたデザインパターンの中では一番好きなコードです。

参考資料