在 Go 语言中,匿名字段(也称为嵌入字段)可以用来实现继承的效果。当你在一个结构体中匿名嵌入另一个结构体时,嵌入结构体的方法会被提升到外部结构体中。这意味着你可以直接通过外部结构体调用嵌入结构体的方法。
如果多个嵌入结构体实现了同一个接口方法,那么调用时会根据方法的定义顺序来决定调用哪个方法。具体来说,Go 语言会选择第一个定义的方法。
示例
假设我们有两个结构体 A
和 B
,它们都实现了同一个接口 MyInterface
,然后我们在结构体 C
中匿名嵌入了这两个结构体。
package main
import "fmt"
// MyInterface 接口
type MyInterface interface {
DoSomething()
}
// 结构体 A
type A struct{}
func (a A) DoSomething() {
fmt.Println("A.DoSomething()")
}
// 结构体 B
type B struct{}
func (b B) DoSomething() {
fmt.Println("B.DoSomething()")
}
// 结构体 C 匿名嵌入了 A 和 B
type C struct {
A
B
}
func main() {
c := C{}
var myInterface MyInterface = c
myInterface.DoSomething() // 输出什么?
}
输出结果
在这个例子中,调用 myInterface.DoSomething()
会输出:
A.DoSomething()
解释
- 方法提升:当
C
结构体匿名嵌入了A
和B
时,A
和B
的方法都被提升到了C
中。 - 方法冲突:由于
A
和B
都实现了DoSomething
方法,因此C
中会有两个同名的方法。 - 方法选择:在 Go 语言中,当多个嵌入字段中有同名方法时,会优先选择第一个定义的方法。在这个例子中,
A
是第一个被嵌入的字段,因此A
的DoSomething
方法会被调用。
更多示例
为了进一步说明这一点,我们可以添加更多的嵌入字段来观察方法的选择顺序。
package main
import "fmt"
// MyInterface 接口
type MyInterface interface {
DoSomething()
}
// 结构体 A
type A struct{}
func (a A) DoSomething() {
fmt.Println("A.DoSomething()")
}
// 结构体 B
type B struct{}
func (b B) DoSomething() {
fmt.Println("B.DoSomething()")
}
// 结构体 D
type D struct{}
func (d D) DoSomething() {
fmt.Println("D.DoSomething()")
}
// 结构体 C 匿名嵌入了 A、B 和 D
type C struct {
A
B
D
}
func main() {
c := C{}
var myInterface MyInterface = c
myInterface.DoSomething() // 输出什么?
}
输出结果
在这个例子中,调用 myInterface.DoSomething()
会输出:
A.DoSomething()
解释
- 方法提升:
A
、B
和D
的DoSomething
方法都被提升到了C
中。 - 方法选择:由于
A
是第一个被嵌入的字段,因此A
的DoSomething
方法会被优先调用。
扩展
当继承类本身也实现了对应方法时,优先使用本身实现的方法
// MyInterface 接口
type MyInterface interface {
DoSomething() string
}
// 结构体 A
type A struct{}
func (a A) DoSomething() string {
fmt.Println("A.DoSomething()")
return "A"
}
// 结构体 B
type B struct{}
func (b B) DoSomething() string {
fmt.Println("B.DoSomething()")
return "B"
}
// 结构体 C 匿名嵌入了 A 和 B
type C struct {
A
B
}
func (c C) DoSomething() string {
fmt.Println("C.DoSomething()")
return "C"
}
func TestEnhanceStrcutC(t *testing.T) {
var baseC C
// 这里将会调用func (c C) DoSomething() string
if "C" != baseC.DoSomething() {
t.Error("DoSomething failed.")
}
}
总结
在 Go 语言中,当一个结构体匿名嵌入了多个实现相同接口的结构体时,调用该接口方法时会优先选择自己实现的方法,如果自己没有实现该方法,就按照顺序从上到下找到第一个定义的方法。方法的定义顺序决定了调用哪个方法。但是为了避免歧义和提高代码的可读性,建议在设计时尽量避免这种情况,或者在外部结构体中显式地实现接口方法。