1. 复合
Go 语言不支持传统面向对象中的"继承"(inheritance) ,但它通过 组合(composition) 和 结构体嵌入(embedding) 实现代码复用,这种方式更灵活、更安全,也符合 Go 的设计哲学:"组合优于继承"。
1.1. 结构体嵌入(匿名字段)
Go 允许在一个结构体中匿名嵌入另一个结构体,从而自动获得被嵌入结构体的方法和字段。
package utils
import (
"testing"
"fmt"
)
// 定义上层的结构体
type Car struct {
Name string
}
func (car *Car) Run(){
fmt.Printf("%s 开始运行了", car.Name)
}
/// -------------------
/// 定义实际的结构体
type XiaomiCar struct {
Car
Price float32
}
func (xiaomicar XiaomiCar) GetInto(){
fmt.Printf("名称:%s\n价格:%.2f万\n", xiaomicar.Name, xiaomicar.Price)
}
///--------------------
// 还可以重写上层机构体的方法
// func (XiaomiCar *XiaomiCar) Run(){
// fmt.Printf("%s 开始运行了!!!", XiaomiCar.Name)
// }
func TestCar(t *testing.T){
t.Log("=======汽车测试======")
var xiaomicar XiaomiCar = XiaomiCar{
Car: Car{Name:"小米SU7"},
Price:19.9,
}
// 调用自己的方法
xiaomicar.GetInto()
// 调用内嵌的结构体方法
xiaomicar.Run()
}
关键点:
XiaomiCar并不是Car的子类,但通过嵌入,它"拥有"了Car的所有字段和方法。- 这叫 方法提升(method promotion)。
- 如果
XiaomiCar定义了同名方法(如Run),则优先调用自己的(类似"覆盖")
1.2. 多重组合(多"父类")
Go 支持嵌入多个结构体,轻松实现"多重继承"的效果(而不会出现菱形问题)。
package utils
import (
"testing"
"fmt"
)
type Swimmer struct{}
func (s *Swimmer) Swim() {
fmt.Println("Swimming...")
}
type Flyer struct{}
func (f *Flyer) Fly() {
fmt.Println("Flying...")
}
// Duck 既能游泳又能飞
type Duck struct {
Swimmer // 嵌入
Flyer // 嵌入
Name string
}
func TestMult(t *testing.T){
t.Log("=====多重组合=====")
duck := Duck{Name:"黑天鹅鸭子"}
duck.Fly()
duck.Swim()
}
"菱形问题"(Diamond Problem)是多重继承(Multiple Inheritance)中一个经典且棘手的问题,尤其在像 C++ 这样的支持类多重继承的语言中经常出现。
当一个子类 同时继承两个父类 ,而这两个父类又共同继承自同一个祖父类时,就会形成一个"菱形"的继承结构:
A
/ \
B C
\ /
D
此时,如果 A 中有一个方法或字段,D 在调用它时,编译器无法确定应该使用哪一条路径继承下来的版本 ------是从 B 还是从 C?这就产生了歧义,称为 菱形问题。
Go不会出现菱形是因为这是嵌入不是继承;另外如下这种写法会直接编译报错
type A struct{}
func (a A) Hello() { fmt.Println("A") }
type B struct{ A }
type C struct{ A }
type D struct{ B; C } // 嵌入 B 和 C,它们都包含 A
func main() {
d := D{}
d.Hello() // ❌ 编译错误!ambiguous selector d.Hello
}
但是类的接口可以实现多继承
-
接口只是"契约"
-
实现者只要实现方法即可
-
方法名相同但签名一致 → 合法
-
签名不一致 → 编译错误
type Flyer interface { Fly() }
type Runner interface { Run() }
type Super interface {
Flyer
Runner
Fly() // 重复声明 Fly() 没问题
}
总结:为什么说 Go "不能出现菱形继承"?
|-----------|-------------------------------------|
| 语言特性 | Go 的处理方式 |
| 没有类继承 | 只有组合(嵌入),不是"is-a",而是"has-a" |
| 嵌入冲突 | 编译时报错:"ambiguous selector",强制用户显式指定 |
| 接口合并 | 允许方法重叠,只要签名一致,无歧义 |
| 设计哲学 | 显式优于隐式,拒绝模糊行为 |
1.3. 接口 + 组合 = 真正的多态
-
定义接口A
-
B实现接口A
-
C嵌入B,从而拥有了B的方法,又因为B实现了A接口的方法,所以C也变相实现了接口,从而可以实现多态
package utils
import (
"testing"
"fmt"
)
// 1. 定义接口
type Speaker interface {
Speak()
}// 定义实现接口的类
type Animal2 struct{
Name string
}func (a *Animal2) Speak(){
fmt.Println(a.Name,"speak....")
}
// 实际的类Dog2
type Dog2 struct{
Animal2
}
// 实际的类Cat2
type Cat2 struct{
Animal2
}func (c *Cat2) Speak(){
fmt.Println("Cat2", c.Name, "says meow!")
}func SpeakTest(s Speaker){
fmt.Println("--多态---")
s.Speak()
}func TestSpeak(t *testing.T){
t.Log("=-====接口+组合=真正的多态=====")
dog := &Dog2{Animal2{Name:"铁牛"}}
cat := &Cat2{Animal2: Animal2{Name:"铁猫"}}
// 打印结构体
dog.Speak()
cat.Speak()
SpeakTest(dog)
SpeakTest(cat)
}
为什么 **SpeakTest(dog)**可以调用成功?
答案的核心就是因为Animal实现了接口方法,从而实现了接口,也因为Dog2嵌入了Animal也是隐式实现了接口的方法Speak() 所以也实现了接口,所以这里可以直接实现类型转换Speaker
1.3.1. 接口的隐式实现(Implicit Interface Implementation)
在 Go 中,一个类型只要实现了接口定义的所有方法,就自动"实现"了该接口 ,不需要显式声明 (比如 Java 的 implements)。
定义了接口
type Speaker interface {
Speak()
}
而 *Dog2 类型是否实现了 Speak() 方法?
Dog2嵌入了Animal2Animal2有方法:func (a *Animal2) Speak()- 由于 方法提升(method promotion) ,
*Dog2自动拥有了Speak()方法
因此:
var _ Speaker = (*Dog2)(nil) // 这行不会报错!说明 *Dog2 实现了 Speaker
所以 dog(类型为 *Dog2)可以作为 Speaker 接口传给 SpeakTest 函数。
1.3.2. 结构体嵌入带来方法提升(Method Promotion)
当这样定义:
type Dog2 struct {
Animal2 // 匿名嵌入
}
Go 会自动把 Animal2 的所有 字段和方法 "提升"到 Dog2 层级(前提是不冲突)。
也就是说:
dog.Name等价于dog.Animal2.Namedog.Speak()等价于dog.Animal2.Speak()
而且这个 Speak() 方法的接收者虽然是 *Animal2,但 Go 允许通过 *Dog2 调用它,因为嵌入关系建立了"拥有"关系。
📌 注意:提升后的方法,其接收者类型仍然是原始类型(这里是 *Animal2),但调用语法上可以直接用外层类型。
1.3.3. 🔍 验证:*Dog2 是否真的实现了 Speaker?
你可以加一行编译时检查:
func TestSpeak(t *testing.T) {
// 编译期验证:确保 *Dog2 实现了 Speaker
var _ Speaker = (*Dog2)(nil)
var _ Speaker = (*Cat2)(nil)
// ...其余代码
}
如果没实现,这行会编译失败。而你的代码能运行,说明确实实现了。
1.3.4. 🧠 补充:值 vs 指针接收者的影响
注意你的方法是定义在 指针接收者 上的:
func (a *Animal2) Speak()
这意味着:
*Animal2实现了SpeakerAnimal2(值类型)没有 实现Speaker
同样:
*Dog2实现了Speaker(因为可以调用(*Animal2).Speak())Dog2(值类型)没有 实现Speaker
所以你必须传 指针:
dog := &Dog2{...} // ✅ 正确:*Dog2 实现了 Speaker
SpeakTest(dog) // OK
// 如果写成:
dog2 := Dog2{...}
SpeakTest(dog2) // ❌ 编译错误!Dog2(值)没有 Speak() 方法(因为 Speak 是指针方法)
1.3.5. ✅ 总结:为什么 SpeakTest(dog) 能成功?
|---------------|--------------------------------------------------|
| 原因 | 说明 |
| 1. 方法提升 | Dog2嵌入 Animal2,自动获得 Speak()方法 |
| 2. 接口隐式实现 | *Dog2有 Speak()方法 → 自动满足 Speaker接口 |
| 3. 传的是指针 | dog := &Dog2{...}是 *Dog2,而 Speak()是指针方法,匹配 |
💡 这正是 Go "组合 + 接口" 实现多态的经典范式:无需继承,也能统一行为。
