Golang 类型嵌入
7.4 类型嵌入
这条“优先使用对象组合而非类继承”的软件工程准则,最早可追溯到 1994 年 Erich Gamma 等四人(俗称“四人帮”)所著的《设计模式》经典著作。虽然 Go 语言没有传统面向对象的继承机制,但通过以下原生特性实现了更优雅的代码复用:
type Employee struct {
Name string
ID string
}
func (e Employee) Description() string {
return fmt.Sprintf("%s (%s)", e.Name, e.ID)
}
type Manager struct {
Employee // 匿名嵌入字段,等同于继承 Employee 类型
Reports []Employee
}
func (m Manager) FindNewEmployees() []Employee {
// do business logic
}
请注意,Manager 包含一个类型为 Employee 的字段,但该字段没有分配名称。这使得 Employee 成为一个嵌入字段。在嵌入字段上声明的任何字段或方法都会被提升到包含结构体(containing struct)(即 Manager)中,并且可以直接在它上面调用。这使得以下代码有效:
m := Manager{
Employee: Employee{
Name: "Bob Bobson",
ID: "12345",
},
Reports: []Employee{},
}
fmt.Println(m.ID) // 访问嵌入字段的属性:prints 12345
fmt.Println(m.Description()) // 调用嵌入字段的方法:prints Bob Bobson (12345)
可以在结构体中嵌入任何类型的字段,而不仅仅是另一个结构体。这会将嵌入类型上的方法提升至包含结构体(containing struct)中。例如,如果结构体嵌入一个实现了
io.Reader接口的类型,可以使该结构体也自动实现io.Reader接口。
如果包含结构体具有与嵌入字段同名的属性(即字段)或方法,则需要使用嵌入字段的类型来引用被遮挡的字段或方法。如果有像这样定义的类型:
type Inner struct {
X int
}
type Outer struct {
Inner
X int
}
则只能通过显式指定 Inner 来访问 Inner 上的 X 字段:
o := Outer{
Inner: Inner{
X: 10,
},
X: 20,
}
fmt.Println(o.X) // prints 20
fmt.Println(o.Inner.X) // prints 10
7.5 嵌入不是继承
内置的嵌入支持在编程语言中是很少见的(我不认为有其他流行的语言支持它)。许多熟悉继承的开发人员试图将嵌入作为继承来理解,这种想法是错误的。
表现上很像继承:Manager 拥有了 Employee 的所有属性和方法,看起来就像 Manager is-a Employee
不能将一个类型为 Manager 的变量赋值给一个类型为 Employee 的变量。如果想要访问 Manager 中的 Employee 字段,必须显式指定(如下所示):
m := Manager{
Employee: Employee{
Name: "Bob Bobson",
ID: "12345",
},
Reports: []Employee{},
}
var eFail Employee = m // 不能将 Manager 赋值给 Employee,编译错误
var eOK Employee = m.Employee // ok!
运行上述代码,将得到编译错误:
cannot use m (type Manager) as type Employee in assignment
此外,在 Go 语言中没有具体类型(Concrete Type)的动态分派(Dynamic Dispatch)。嵌入字段上的方法不知道它自己是嵌入的。如果嵌入字段上的一个方法调用嵌入字段上的另一个方法,并且包含结构体(containing struct)具有同名方法,则会调用嵌入字段上的同名方法,而不是包含结构体(containing struct)的方法:
type Inner struct {
A int
}
func (i Inner) IntPrinter(val int) string {
return fmt.Sprintf("Inner: %d", val)
}
func (i Inner) Double() string {
return i.IntPrinter(i.A * 2)
}
type Outer struct {
Inner
S string
}
func (o Outer) IntPrinter(val int) string {
return fmt.Sprintf("Outer: %d", val)
}
func main() {
o := Outer{
Inner: Inner{
A: 10,
},
S: "Hello",
}
fmt.Println(o.Double())
}
运行此代码将生成以下输出:
Inner: 20
当一个结构体 A 嵌入了另一个结构体 B 作为嵌入字段时,虽然不能将外部类型 A 视为内部类型 B,但嵌入字段 B 的方法会被提升到包含结构体 A 的方法集中,成为包含结构体 A 的一部分。因此,如果嵌入结构体实现了某个接口所需的所有方法,那么包含结构体也会自动实现该接口。简单来说,就是如果一个结构体 A 嵌入了另一个结构体 B,而结构体 B 实现了接口 I 所需的所有方法,那么结构体 A 也实现了接口 I,因为结构体 B 的方法被提升到了结构体 A 上。
评论