在 Go 中,嵌入(embedding)是一种机制,允许通过将一个结构体嵌入到另一个结构体中来“继承”结构体的行为,而无需使用显式的继承,例如在 OOP 中。嵌入的结构体成为新结构体的嵌入字段(匿名字段)。
在这种情况下,嵌入结构体的所有方法都成为新结构体的方法,仿佛被“继承”了一样。
示例:
package main import "fmt" type Animal struct { Name string } func (a Animal) Speak() { fmt.Println("My name is", a.Name) } type Dog struct { Animal // embedding, 匿名字段 Breed string } func main() { d := Dog{ Animal: Animal{Name: "Rex"}, Breed: "Shepherd", } d.Speak() // 继承的方法! fmt.Println(d.Name) // 字段可以直接访问 }
嵌入和将结构体作为命名字段的常规包含之间的主要区别是:
“如果嵌入结构体和外部结构体包含相同名称的字段,将使用哪个字段,如何访问它们?”
许多人认为只会使用“外部”字段,但实际情况是:
示例:
type Base struct { Name string } type Child struct { Base // embedding Name string // 相同名称的字段 } c := Child{Base: Base{Name: "Base"}, Name: "Child"} fmt.Println(c.Name) // Child fmt.Println(c.Base.Name) // Base
故事
在项目中嵌入了标准的日志结构,然后在子类型中定义了
Logger字段。结果,调用d.Logger.Info()的行为与预期不符,因为对嵌入字段的访问在同名字段之间“丢失”了。
故事
开发人员试图在子结构中重写方法,但方法无法“重写”——只是出现了一个新方法,父类的方法仍然可以通过
Child.Base.Method()访问。这导致部分代码使用了“旧的”版本的方法,而期待不同的逻辑。
故事
在使用嵌套结构体进行 JSON 序列化时出现了意外情况:“嵌入结构体”的字段出现在根对象中。由于不知道嵌入如何影响 marshaling,结构体序列化不正确,破坏了 API 的向后兼容性。