深色模式
Go语言结构体与组合
概述
Go 的 struct 很朴素,没有继承层级、没有构造器语法、也没有字段访问修饰符那套复杂体系。但也正因为它朴素,很多设计选择会直接暴露出来:数据到底怎么组织,哪些字段该导出,哪些行为该通过组合带进来。
这篇文章的重点不是罗列 struct 语法,而是把 Go 里“数据结构”和“组合关系”这条主线讲清楚。方法会在下一篇单独讲,这里先只看数据和组织方式。
struct 是 Go 最常见的数据组织方式
最普通的结构体定义如下:
go
type User struct {
ID int64
Name string
Age int
}和 map 相比,struct 更强调:
- 字段集合固定
- 各字段语义明确
- 编译期可检查
零值不是边角规则,而是 Go 设计的一部分
Go 的结构体有天然零值:
go
var u User这时:
u.ID是0u.Name是""u.Age是0
这意味着一个很重要的使用习惯:很多类型在未显式初始化前,先得问一句“零值是否可用”。
结构体字面量更推荐按字段名写
go
u := User{
ID: 1,
Name: "alice",
Age: 18,
}按字段名写的好处很直接:
- 可读性更好
- 字段顺序变化时更稳
- 减少位置参数式猜谜
导出字段和未导出字段
Go 靠首字母大小写区分导出性:
go
type user struct {
id int64
Name string
}这里:
id不导出Name导出
这不是单纯命名习惯,而是包边界上的访问控制方式。
指针字段和值字段不是随便放的
结构体字段到底该直接放值,还是放指针,通常取决于两件事:
- 这个字段是否天然可以没有
- 是否需要共享同一份对象
例如:
go
type Order struct {
ID int64
Buyer User
Coupon *Coupon
}Buyer 更像订单内嵌的值,Coupon 更像可选对象入口。
结构体嵌入是组合,不是继承
Go 常说“组合优于继承”,真正落地时,最常见的语法就是结构体嵌入:
go
type Logger struct{}
type Service struct {
Logger
Name string
}这里 Service 嵌入了 Logger。外部可以直接通过 Service 使用被提升的方法或字段,但这不等于 Go 引入了一套类继承体系。
更准确的理解是:
Service拥有一个Logger字段- 这个字段是匿名的
- 相关成员可以被提升访问
组合的价值在于拼能力,不是造层级
例如:
go
type Reader struct{}
type Writer struct{}
type FileStore struct {
Reader
Writer
}这里的重点不是“FileStore 继承了什么”,而是“FileStore 由多个能力块组合而成”。
匿名字段带来的是提升,不是魔法
go
type Address struct {
City string
}
type User struct {
Address
}这时可以直接写:
go
u.City但真正的关系仍然是:
go
u.Address.City提升只是访问上的便利,不代表嵌入字段消失了。
何时该拆出独立类型
如果一组字段在多个地方重复出现,或者本身已经有明确语义,通常就值得拆出独立类型:
go
type Address struct {
Province string
City string
}
type User struct {
Name string
Address Address
}这样做的价值是:
- 语义更清楚
- 复用更自然
- 后续给这个类型加方法也更顺
一条够用的判断线
设计 Go 结构体时,按这个顺序想通常比较稳:
- 这组数据是否应该成为独立类型。
- 哪些字段该暴露给包外。
- 哪些字段天然可缺省,是否需要用指针表达。
- 这段复用关系是字段包含,还是匿名嵌入。
Go 的 struct 不复杂,但一旦把这些边界想清楚,后面的方法、接口和并发设计都会顺很多。
