Goのinterface/structのembedding
golangGoには継承が存在しないが、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
}
}