嵌套与嵌入 - 结构体组合
Go 通过结构体嵌套和嵌入实现代码复用,这是 Go 组合优于继承理念的体现。掌握匿名嵌入和字段提升是编写优雅 Go 代码的关键。
嵌套结构体
📝 命名嵌套
package main
import "fmt"
type Address struct {
City string
District string
Street string
}
type Person struct {
Name string
Age int
Address Address // 命名嵌套
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "Beijing",
District: "Chaoyang",
Street: "Main St",
},
}
// 访问嵌套字段
fmt.Printf("%s lives in %s, %s\n",
p.Name, p.Address.City, p.Address.District)
}
匿名嵌入
📝 字段提升
package main
import "fmt"
type Address struct {
City string
Street string
}
type Person struct {
Name string
Age int
Address // 匿名嵌入,字段提升
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "Beijing",
Street: "Main St",
},
}
// 字段提升:可以直接访问
fmt.Println(p.City) // 等价于 p.Address.City
fmt.Println(p.Street) // 等价于 p.Address.Street
// 也可以显式访问
fmt.Println(p.Address.City)
}
💡 嵌入要点
- 字段提升: 嵌入类型的字段提升到外层
- 访问方式: 可以直接访问或通过嵌入类型访问
- 方法提升: 嵌入类型的方法也会提升
- 名称冲突: 同名字段会隐藏嵌入字段
嵌入类型的方法
📝 方法提升
package main
import "fmt"
type Logger struct {
prefix string
}
func (l Logger) Info(msg string) {
fmt.Printf("[%s] INFO: %s\n", l.prefix, msg)
}
func (l Logger) Error(msg string) {
fmt.Printf("[%s] ERROR: %s\n", l.prefix, msg)
}
type Service struct {
Name string
Logger // 嵌入 Logger
}
func main() {
svc := Service{
Name: "UserService",
Logger: Logger{prefix: "SVC"},
}
// 方法提升:可以直接调用
svc.Info("Service started")
svc.Error("Something went wrong")
// 也可以显式调用
svc.Logger.Info("Explicit call")
}
方法重写
📝 覆盖嵌入方法
package main
import "fmt"
type Base struct{}
func (b Base) Greet() {
fmt.Println("Hello from Base")
}
type Derived struct {
Base // 嵌入 Base
}
// 重写 Greet 方法
func (d Derived) Greet() {
fmt.Println("Hello from Derived")
}
func main() {
d := Derived{}
// 调用重写的方法
d.Greet() // Hello from Derived
// 调用嵌入类型的方法
d.Base.Greet() // Hello from Base
}
多重嵌入
📝 嵌入多个类型
package main
import "fmt"
type A struct {
AField string
}
type B struct {
BField int
}
type C struct {
A // 嵌入 A
B // 嵌入 B
CField string
}
func main() {
c := C{
A: A{AField: "a-value"},
B: B{BField: 42},
CField: "c-value",
}
// 访问提升的字段
fmt.Println(c.AField) // a-value
fmt.Println(c.BField) // 42
fmt.Println(c.CField) // c-value
}
⚠️ 名称冲突
// 如果多个嵌入类型有同名字段
type A struct { Name string }
type B struct { Name string }
type C struct {
A
B
}
// c.Name 会编译错误!
// 必须显式指定:c.A.Name 或 c.B.Name
最佳实践
✅ 嵌入使用建议
- 组合优于继承: 使用嵌入实现代码复用
- 语义清晰: 嵌入应该有"is-a"关系
- 避免深度嵌套: 超过 2 层考虑重构
- 注意冲突: 避免同名字段冲突