Edited at

Goの構造体のフィールドに関数を埋め込んでDIを実現してみる


構造体に関数を埋め込むとは

以下のように構造体にメソッドを生やすことが多いと思う。

package main

import (
"fmt"
)

type Hoge struct {
}

func (h *Hoge) Foo(bar string) string {
return fmt.Sprintf("%sだ!", bar)
}

func main() {
h := &Hoge{}

fmt.Println(h.Foo("sekky0905"))
}

実行結果

sekky0905だ!

実は、構造体のフィールドに関数を埋め込むことができる。

The Go Programming Language Specification - The Go Programming Languageにも、以下のような例が存在している。


// A struct with 6 fields.
struct {
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}

引用元 : The Go Programming Language Specification - The Go Programming Language

なので、以下のようなコードを実装することができる。


import "fmt"

type Hoge struct {
Foo func(bar string) string
}

func main() {
f := func(bar string) string {
return fmt.Sprintf("%sだ!", bar)
}

h := &Hoge{Foo: f}

fmt.Println(h.Foo("sekky0905"))
}

実行結果

sekky0905だ!


使い所

会社の先輩にこの埋め込みの関数の使い所を聞いたところ、「関数をDIで渡したいときとかに使うと便利だよ」

と教えていただいたので、実際に簡単にサンプルを書いてみた。


サンプル

Strategyパターンもどきを実装してみた。

Strategyパターンは、以下のようなもの。


Strategy パターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。


引用元 : Strategy パターン - Wikipedia

インターフェースと構造体の埋め込みを用いてDIしたパターンと、関数の埋め込みを用いてDIしたパターンを実装してみたい。


インターフェースと構造体の埋め込みを用いてDIしたパターン


package main

import (
"fmt"
)

type Strategy interface {
ExecuteStrategy()
}

type StrategyA struct {
Name string
}

func (s *StrategyA) ExecuteStrategy() {
fmt.Printf("%s実行したぜ~!\n", s.Name)
}

func NewStrategyA(name string) Strategy {
return &StrategyA{
Name: name,
}
}

type StrategyB struct {
Name string
}

func (s *StrategyB) ExecuteStrategy() {
fmt.Printf("%s実行したよ~!\n", s.Name)
}

func NewStrategyB(name string) Strategy {
return &StrategyB{
Name: name,
}
}

type Player struct {
Strategy Strategy // Strategy型にすることで、後で埋め込む際にStrategy型を実装した任意の型を埋め込むことができる
}

func main() {
sa := NewStrategyA("StrategyA")
p1 := &Player{
Strategy: sa,
}

p1.Strategy.ExecuteStrategy()

sb := NewStrategyB("StrategyB")
p2 := &Player{
Strategy: sb,
}

p2.Strategy.ExecuteStrategy()
}

実行結果

StrategyA実行したぜ~!

StrategyB実行したよ~!

ここでのポイントは、Playerに埋め込ませるStrategyフィールドを具体的なStrategyAやStrategyBではなく、Strategy型で定義したことである。こうすることで、Playerをインスタンス化するときに使用するStrategyを選択する余地が生まれる。

(StrategyのフィールドをStrategyAやStrategyBにしちゃうと、どちらかしか埋め込めない)


関数の埋め込みを用いてDIしたパターン


package main

import (
"fmt"
)

type Player struct {
ExecuteStrategy func(name string)
}

func ExecuteStrategyA(name string) {
fmt.Printf("%s実行したぜ~!\n", name)
}

func ExecuteStrategyB(name string) {
fmt.Printf("%s実行したよ~!\n", name)
}

func main() {
p1 := &Player{
ExecuteStrategy: ExecuteStrategyA,
}
p1.ExecuteStrategy("StrategyA")

p2 := &Player{
ExecuteStrategy: ExecuteStrategyB,
}
p2.ExecuteStrategy("StrategyB")
}

結果

StrategyA実行したぜ~!

StrategyB実行したよ~!

ここでのポイントは、PlayerのExecuteStrategyフィールドの関数のシグネチャを満たしたものであれば、どんな関数でもPlayerに埋め込むことができるということである。

参考 : Strategy パターン - Wikipedia


参考にさせていただいたURL

The Go Programming Language Specification - The Go Programming Language

Strategy パターン - Wikipedia