模块课程(3/5)
组合(嵌入)
Go 没有继承。重用的惯用答案是 组合,特别是嵌入:包括一种类型 在另一个中而不给它一个字段名称。领域和方法 嵌入类型得到提升,变得可以直接访问,就像 它们属于外型。
结构嵌入
type Animal struct {
Name string
}
func (a Animal) Greet() string {
return "ciao da " + a.Name
}
type Dog struct {
Animal // embedding — NO field name
Breed string
}
c := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Pastore",
}
fmt.Println(c.Name) // "Rex" — promoted from Animal
fmt.Println(c.Greet()) // "ciao da Rex" — promoted method
fmt.Println(c.Breed) // own fieldThe embedded field is still accessible by the type's name:
c.Animal.Name works and is identical to c.Name.当需要时
有碰撞。
覆盖(部分):方法“shadowing”
If the outer type defines a method with the same name as the promoted 一、其方法“胜”。从内部您始终可以到达嵌入的 一种通过类型名称:
func (c Dog) Greet() string {
return "BAU! " + c.Animal.Greet()
}它不是多态继承:它只是词法名称解析。 对于多态性,您可以使用接口(模块 6)。
多重嵌入
您可以嵌入多种类型:
type Logger struct{ /*...*/ }
func (l Logger) Log(msg string) { /*...*/ }
type Timer struct{ /*...*/ }
func (t Timer) Now() time.Time { /*...*/ }
type Service struct {
Logger
Timer
Name string
}
s := Service{Name: "api"}
s.Log("start") // promoted from Logger
s.Now() // promoted from Timer如果两个嵌入类型具有相同名称的方法,则直接访问
变得不明确,编译器需要显式形式
(s.Logger.Foo() 或 s.Timer.Foo())。
接口嵌入
接口也可以“嵌入”(参见模块 6):
type ReadWriter interface {
io.Reader
io.Writer
}它相当于 Reader 和 Writer 方法的联合。
何时使用显式组合(而不是嵌入)
If you want a named field and no auto-promotion, use plain 组成:
type Dog struct {
Anim Animal // NORMAL field, no promotion
Breed string
}
c.Anim.Name // you need the field nameEmbedding is a handy delegation tool, not a design choice strictly better than explicit composition: use it when you really want 提升内部类型的API。
尝试一下
定义 Dog,嵌入 Animal(嵌入,无字段名称)并具有 Breed 字符串字段。
显示提示
嵌入:编写类型而不给出字段名称。
3 次尝试后可用的解决方案
向 Animal 添加一个 Greet() 字符串方法,并直接在 Dog 变量上调用它(方法升级)。
显示提示
由于嵌入,Animal 上定义的方法被提升为 Dog。
3 次尝试后可用的解决方案
Go 支持经典继承吗?
type B struct { /* extends A? */ }回顾
- 无继承:您使用嵌入进行组合。
- 嵌入 = 在结构中写入不带字段名称的 TYPE。
- 嵌入类型的字段和方法得到提升。
- 显式访问始终可用:
outer.Inner.Field。 - “词法”覆盖:外部类型的方法隐藏了升级的方法。
- 嵌入≠ is-a:对于多态性,使用接口。