Go の struct と interface で Embedding
Effective Go の Embedding の内容を試してみます。
Go では embedding を利用して継承のようなことができますが、struct と interface の違いが今ひとつ理解できていなかったため、実際にコードを書いてコンパイラに怒られながら、どういう違いがあるのか試していきたいと思います。
- 確認方法
- interface を interface に埋め込む
- struct を struct に埋め込む
- struct を interface に埋め込む(不可)
- interface を struct に埋め込む
確認方法
こんな感じで embedding を利用して BaseMethod
と SuperMethod
を実装するパターンを色々見ていきます。
type baseInterface interface { BaseMethod() string } type superInterface interface { baseInterface SuperMethod() string }
実装の確認用兼、interface を満たしているかの確認用の関数を用意します。
func printSuper(name string, v superInterface) { fmt.Println(name+".BaseMethod ->", v.BaseMethod()) fmt.Println(name+".SuperMethod ->", v.SuperMethod()) }
interface を interface に埋め込む
type baseInterface interface { BaseMethod() string } // 一応 baseInterface の方の実装も作っておく type implOfBaseInterface struct{} func (implOfBaseInterface) BaseMethod() string { return "implOfBaseInterface.BaseMethod" } type superInterface interface { baseInterface SuperMethod() string } type implOfSuperInterface struct{} // interface を interface に埋め込んだ場合 BaseMethod の定義は必要 func (i implOfSuperInterface) BaseMethod() string { return "implOfSuperInterface.BaseMethod" } func (implOfSuperInterface) SuperMethod() string { return "implOfSuperInterface.SuperMethod" }
impleOfSuperInterface
側でも BaseMethod
の実装が必要になります。
実装を参照する先も無いので当たり前と言えば当たり前ですね。
func main() { printSuper("implOfSuperInterface", implOfSuperInterface{}) }
してみると
implOfSuperInterface.BaseMethod -> implOfSuperInterface.BaseMethod implOfSuperInterface.SuperMethod -> implOfSuperInterface.SuperMethod
これも impleOfSuperInterface
に実装したものがそのまま呼ばれて、当たり前の結果ですね。
このパターンの埋め込みをするメリットとしては、interface 側のメソッドの定義を省略できることくらいでしょうか。
struct を struct に埋め込む
type baseStruct struct{} func (b baseStruct) BaseMethod() string { return "baseStruct.BaseMethod" } type superStruct struct { baseStruct } // この場合 BaseMethod は省略できるが実装してもいい // func (s superStruct) BaseMethod() string { // // 明示的に baseStruct.BaseMethod を呼ぶこともできるし(あまり意味なさそう) // return s.baseStruct.BaseMethod() // // 他の処理を定義してもいい // return "superStruct.BaseMethod" // } func (s superStruct) SuperMethod() string { return "superStruct.SuperMethod" }
struct を struct に埋め込んだ場合は superStruct.BaseMethod
の実装を省略できるみたいです。
func main() { printSuper("superStruct", superStruct{}) }
してみると
superStruct.BaseMethod -> baseStruct.BaseMethod superStruct.SuperMethod -> superStruct.SuperMethod
superStruct.BaseMethod
で baseStruct.BaseMethod
が呼ばれていますね。
struct を interface に埋め込む(不可)
type structEmbeddedInterface interface { baseStruct SuperMethod() } type implOfStructEmbeddedInterface struct{} func (implOfStructEmbeddedInterface) SuperMethod() { fmt.Println("structEmbeddedInterface.SuperMethod") }
これは不可能なパターンで、コンパイル時に interface contains embedded non-interface baseStruct
と怒られます。
interface を struct に埋め込む
type interfaceEmbeddedStruct struct { baseInterface } func (interfaceEmbeddedStruct) SuperMethod() string { return "interfaceEmbeddedStruct.SuperMethod" }
この場合は interfaceEmbeddedStruct.BaseMethod
の実装をしなくてもコンパイルは通ります。しかし、ランタイムで panic: runtime error: invalid memory address or nil pointer dereference
というエラーになります。一番タチが悪いですね。
このパターンの活用事例は悩ましいですが、考察されているページがあったので時間のある時に読みたいと思います。 https://horizoon.jp/post/2019/03/16/go_embedded_interface/
今回触って改めて、このGo言語における embedding は手を動かさないと理解できないなと思いました。単純にパターンが多いので網羅しづらいのと、網羅されても今度は長大になるため読み解く時間がないので、手元で試した方が速いなと…