>

通过在一个 struct 中内嵌另一个无名的变量,可以使得当前 struct 类型具有那个无名变量的公开成员(包括成员变量和成员函数)。也就是说当前对象可以直接访问内嵌无名变量的成员变量和成员方法。
Go 语言编译期是通过一种叫 “promotion” 的方式来达到这种效果的。

promotion 的原理就是,编译期会为当前 struct 生成一个与内嵌变量的成员函数一样的函数,并在这个函数里面调用内嵌变量的成员函数来达到效果。

1、内嵌 struct 匿名成员

1)内嵌单个匿名成员

比如,我们有一个作为基类使用的 struct,定义了一些通用的方法,然后我们在具体的子类中内嵌定义一个这个基类类型的匿名成员,这样,在子类中就可以使用基类的方法了。

如下:

作为基类类型的 Ball

1
2
3
4
5
6
7
8
type Ball struct {
Radius int
Material string
}

func (b Ball) Bounce() {
fmt.Println("bounce!")
}

子类 Football 将 Ball 内嵌为匿名成员

1
2
3
type Football struct {
Ball
}

我们把 Football 打印出来,看看其内部结构。

1
2
fb := Football{}
fmt.Printf("fb = %+v\n", fb)

fb = {Ball:{Radius:0 Material:}}

现在,子类可以直接调用基类的方法了。

1
fb.Bounce()

当然,也可以通过引用内嵌类型名来调用 fb.Ball.Bounce()

上面,我们内嵌的无名成员,我们还可以内嵌无名成员变量的指针,如下

1
2
3
type Football struct {
*Ball
}

这样的话,我们实例化 Football 的时候就需要同时初始化其成员指针。如果没有初始化 *ball 指针的话,那么直接调用其方法会 panic。

2) 内嵌多个匿名成员

如果内嵌多个匿名 struct 成员,而这些匿名成员如果定义了相同的函数的话,那么调用这些函数就会导致编译错误:”ambiguous selector fb.Bounce”。
此外,注意,只要函数名相同,就会导致上述错误,不论参数类型和个数怎么样。

如,以下就会导致 ambiguous

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Fan struct {
Size int
}

func (f Fan) Bounce(height int) {
fmt.Printf("Fan bounce! height: %v\n", height)
}

type Football struct {
Ball
Fan
}

func main() {
fb := Football{}
fb.Bounce(100)
}

还需要注意的是,只要没有调用 fb.Bounce(),那么是可以编译通过的,而且,如果 Fan 还定义了其他函数的话,只要没有直接调用二者相同的函数,一切都没问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
func (f Fan) Rotate() {
fmt.Println("Fan rotate!")
}

type Football struct {
Ball
Fan
}

func main() {
fb := Football{}
fb.Rotate() // ok!
}

2、内嵌 interface

struct 可以内嵌匿名的 interface 接口类型。我们知道,通常 interface 接口类型会声明一些方法,这些方法由具体的 implementer struct 去实现。如果我们在一个 struct 中定义了匿名的 interface{} 接口类型,那么我们在实例化这个 struct 的时候,应该同时实例化那个内嵌的 interface{} 对象,否则 interface 就会是 nil 的,调用该 interface 所声明的方法就会导致 panic。

1
2
3
4
5
6
7
type Bouncer interface {
Bounce()
}

type Football struct {
Bouncer
}

这里,Bouncer 作为一个匿名的 interface 成员,如果我们实例化 Football 的时候没初始化这个 Bouncer 为具体的 implementor 类型的话,那么就是 nil。

1
2
3
4
func main() {
fb := Football{}
fmt.Printf("%v\n", fb)
}

结果

{<nil>}

因此,我们应该在实例化 Football 对象的时候,初始化它的 interface 成员。如下:

1
2
3
4
5
6
7
8
9
10
11
12
type Ball struct {
Radius int
}

func (b *Ball) Bounce() {
fmt.Println("ball bounce!")
}

func main() {
fb := Football{&Ball{10}}
fmt.Printf("%v\n", fb)
}

打印结果

{{10}}

注意点:

如果当前 struct 定义了一个变量和一个内嵌的匿名变量,而该变量名字与其内嵌匿名变量的成员函数名字一样,虽然编译可以通过,但是调用时会出错。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (b Ball) Bounce() {
fmt.Println("ball bounce!")
}

type Football struct {
Ball
Bounce int
}

func main() {
fb := Football{}
fmt.Println("fb.Bounce is: ", fb.Bounce)
fb.Bounce() // runtime error: cannot call non-function bb.Bounce (type int)
}

3、继承类通过内嵌匿名成员的方式来变成接口的实现者

如果一个基类 struct 已经实现了某接口,那么继承类可以通过把其内嵌为匿名成员,从而也变成一个 implementer。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

type Vollyball struct {
Ball // an struct
}

type Basketball struct {
Bouncer // an interface
}

func TestDuckType(bc Bouncer) {
bc.Bounce()
}

func main() {
vb := Vollyball{} // Vollyball becomes a Bouncer
TestDuckType(vb)

bb := Baskball{Ball{}} // Basketball becomes a Bouncer
TestDuckType(pb)
}

如果,内嵌的两个匿名变量都实现了同一个接口,那么调用就会出现冲突,编译会失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

type Tennis struct {
Color string
}

func (tennis Tennis) Bounce(){
fmt.Println("tennis bounce!")
}

type Basketball struct {
Ball // an struct
Bouncer // an interface
}

func TestDuckType(bc Bouncer) {
bc.Bounce()
}

func main() {
bb := Baskball{Ball{}} // Basketball becomes a Bouncer
TestDuckType(pb)
}

出现如下错误:

1
2
Basketball.Bounce is ambiguous
cannot use bb (type Basketball) as type Bouncer in argument to TestDuckType: Basketball does not implement Bouncer (missing Bounce method)