Goのinterface/structのembedding

golang

Goには継承が存在しないが、structを埋めることで透過的に処理を委譲することができる。

https://golang.org/doc/effective_go.html#embedding

挙動

interfaceにinterfaceをembed

type I interface {
	Hoge()
}

type J interface {
	Fuga()
}

type K interface {
	I
	J
}

interface KはIとJを合わせたものになる。IとJに重複する関数がある場合はエラーになる。

type L struct {
}

func (l L) Hoge() {
	fmt.Println("hoge")
}
func (l L) Fuga() {
	fmt.Println("fuga")
}
var k K
k = L{}
k.Hoge()
k.Fuga()

structにinterfaceをembed

通常このようになるところ、

type K interface {
	Hoge()
	Fuga()
}

type M struct {
	k K
}

m := M{L{}}
m.k.Hoge()

embedすると m.Hoge() のようにinterfaceの関数を透過的に呼ぶことができる。

type M struct {
	K
}

m := M{L{}}
m.Hoge()
// m.K.Hoge() is the same

structにstructをembed

type A struct {
	name string
}

type B struct {
	A
}

func (a A) hoge() {
	fmt.Println("hoge", a.name)
}

上と同様に b.name や、b.hoge() のようにembedしたstructのフィールドや関数を透過的に呼ぶことができる。

b := B{}
b.name = "a"
// b = B{A{name: "a"}} 
b.hoge()
// b.A.hoge() is the same

また、Bにも同名の関数 hoge() を実装すると b.hoge() でAではなくBの hoge() が呼ばれるようになる。

func (b B) hoge() {
	fmt.Println("fuga", b.name)
}
b := B{}
b.name = "piyo"
b.hoge() // => fuga piyo
b.A.hoge() // => hoge piyo

継承との違い

これまでの挙動を見ると一見継承と同じように見える。

type A struct {
	name string
}

type B struct {
	A
}

func (a A) hoge() {
	fmt.Println("hoge", a.name)
}

func (a A) fuga() {
	a.hoge()
}

// override?
func (b B) hoge() {
	fmt.Println("fuga", b.name)
}

しかし、継承では b.fuga() から hoge() を呼ぶと、オーバーライドした b.hoge() が呼ばれ、“fuga” が出力されるはずだが、 あくまで fuga() のレシーバーはAなので、呼ばれるのはAをレシーバーとする方の hoge() となり、“hoge"が出力されることになる。

b := B{}
b.name = "piyo"
b.fuga() // => hoge piyo

また、type Aの変数にBを代入することもできない。

用途

interfaceのデフォルトの実装を用意したstructをembedすることで、各structでは差分だけを実装すればいいようにできる。 ただし、デフォルト実装が他のデフォルト実装された関数に依存している場合、上に書いたレシーバーの都合上、 呼び先の関数だけ実装しても元々の関数の動作は変えられないことに注意が必要。 そういうこともありembedせずに済むなら回避した方が良いと考えている。

type Student interface {
	Plus(x int, y int) int
	Minus(x int, y int) int
}

type defaultStudent struct{}

func (defaultStudent) Plus(x int, y int) int {
	return x + y
}

func (defaultStudent) Minus(x int, y int) int {
	return x - y
}

type BadStudent struct {
	defaultStudent
}

func (BadStudent) Minus(x int, y int) int {
	return 0
}

type GeniusStudent struct {
	defaultStudent
}

func (GeniusStudent) Plus(x int, y int) int {
	if ans, err := strconv.Atoi(fmt.Sprintf("%d%d", x, y)); err != nil {
		fmt.Errorf("genius student fails to %d + %d", x, y)
		return 0
	} else {
		return ans
	}
}

参考

オブジェクト指向言語としてGolangをやろうとするとハマる点を整理してみる - Qiita