Golang组合式继承

Golang composite inheritance

Posted by alovn on December 25, 2021

Go语言中没有继承,但是可以通过组合实现继承。组合的方式可以是值类型也可以是指针类型,那么它们实现继承的方式有什么不同呢?

现有如下main.go的定义,B为值类型组合,C为指针类型组合:

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

type A struct {
    value int
}

func (a A) Value() int {
    return a.value
}

func (a *A) Set(v int) {
    a.value = v
}

type B struct {
    A
}

type C struct {
    *A
}

首先编译以下代码,其中参数 -l 参数禁止内联优化,-p 设置需要的包导入路径:

1
> go tool compile -l -p main main.go

然后通过使用go tool nm查看编译器生成的符号表。nm会输出OBJ文件中定义的符号信息,同样可以分析出Golang为我们自动生成了哪些方法。grep过滤代码段符号对应的T标识:

1
2
3
4
5
6
7
8
9
10
11
12
> go tool nm main.o | grep T

44c1 T main.(*A).Set
5205 T main.(*A).Value
5267 T main.(*B).Set
52c9 T main.(*B).Value
5372 T main.(*C).Set
53d4 T main.(*C).Value
44c0 T main.A.Value
531f T main.B.Value
5431 T main.C.Set
5493 T main.C.Value

可以看到上面的方法列表中有(A).Value方法,而源码中并没有(A).Value这个方法,那么它是哪里来的呢,毫无疑问是编译器生成的。Go的编译器会为接收者为值类型(T)的方法自动生成接收者为指针类型(*T)的方法,一般也称它为“包装方法”。编译器生成的包装方法主要是为了支持接口,(因为接口不能直接使用接受者为值类型的方法)。不过若从可执行文件分析,链接器会把程序中确定不会用到的方法(含包装方法、自己定义的方法)都裁剪掉。

对于组合类型B(值类型组合)可以看到它只有B.Value方法,而没有B.Set方法,这是为什么呢?因为接收者B调用方法时,方法操作的是B的副本,不能获取到嵌入类型A的地址,所以B继承*A的方法是没有意义的,编译器也就没有为它生成Set方法。

对于组合类型C(指针类型组合)则继承了所有方法。

综上可以发现,组合类型无论是嵌入值还是嵌入指针,值接收者方法始终能被继承。而只有在能拿到嵌入对象的地址时,才能继承指针接收者方法。 这就是编译器为组合式继承生成包装方法的规则。