实现继承的原理 #
我们将 嵌入字段的用法的例子代码做一下细微改动:
var sl = make([]byte, len("hello, go"))
s.Read(sl)
fmt.Println(string(sl))
s.Add(5)
fmt.Println(*(s.MyInt))
Read 方法与 Add 方法看起来就是类型 S 方法集合中的方法。但是,这里类型 S 明明没有显式实现这两个方法。
这两个方法来自结构体类型 S 的两个嵌入字段 Reader 和 MyInt。结构体类型 S“继承”了 Reader 字段的方法 Read 的实现,也“继承”了 *MyInt 的 Add 方法的实现。注意,我这里的“继承”用了引号,说明这并不是真正的继承,它只是 Go 语言的一种“障眼法”。
这种“障眼法”的工作机制是这样的,当我们通过结构体类型 S 的变量 s 调用 Read 方法时,Go 发现结构体类型 S 自身并没有定义 Read 方法,于是 Go 会查看 S 的嵌入字段对应的类型是否定义了 Read 方法。这个时候,Reader 字段就被找了出来,之后 s.Read 的调用就被转换为 s.Reader.Read 调用。
这样一来,嵌入字段 Reader 的 Read 方法就被提升为 S 的方法,放入了类型 S 的方法集合。同理 *MyInt 的 Add 方法也被提升为 S 的方法而放入 S 的方法集合。从外部来看,这种嵌入字段的方法的提升就给了我们一种结构体类型 S“继承”了 io.Reader 类型 Read 方法的实现,以及 *MyInt 类型 Add 方法的实现的错觉。
到这里,我们就清楚了,嵌入字段的使用的确可以帮我们在 Go 中实现方法的“继承”。
类型嵌入这种看似“继承”的机制,实际上是一种组合的思想。更具体点,它是一种组合中的代理(delegate)模式,如下图所示:
我们看到,S 只是一个代理(delegate),对外它提供了它可以代理的所有方法,如例子中的 Read 和 Add 方法。当外界发起对 S 的 Read 方法的调用后,S 将该调用委派给它内部的 Reader 实例来实际执行 Read 方法。