学习目标
掌握 struct 在 Go 中的真实定位(数据 + 行为载体)
学会用 组合(composition) 代替继承
正确理解 嵌入(embedding) 的语义与使用边界
在工程中设计“边界清晰、不易失控”的 struct
一、struct 在 Go 中是什么
在 Go 中:
struct 是最基础、最重要的建模工具
它同时承担两种角色:
– 数据结构(字段)
– 行为承载体(方法)
Go 没有 class,但 struct + method + interface = class 能力
二、struct 的基本定义与使用
1. 定义 struct
type User struct {
ID int
Name string
Age int
}2. 创建 struct 实例
u := User{
ID: 1,
Name: "Tom",
Age: 18,
}说明:
- 推荐使用 字段名初始化
- 避免依赖字段顺序
3. 零值 struct
var u User
fmt.Println(u) // {0 "" 0}结论:
Go 的零值是“可用状态”,不是半成品
三、struct 作为参数与返回值
1. struct 作为参数(值拷贝)
func printUser(u User) {
u.Name = "Jack"
}
func main() {
u := User{Name: "Tom"}
printUser(u)
fmt.Println(u.Name) // Tom
}原因:
- struct 默认按值传递
2. struct 指针作为参数(推荐)
func printUser(u *User) {
u.Name = "Jack"
}
func main() {
u := User{Name: "Tom"}
printUser(&u)
fmt.Println(u.Name) // Jack
}工程结论:
参与修改的 struct,必须使用指针
四、struct 的方法设计
1. 方法属于 struct,而不是函数
func (u *User) Rename(name string) {
u.Name = name
}调用:
u.Rename("Jack")说明:
- Go 会自动处理 &u / *u
- 调用方无需关心指针细节
2. 方法接收者选择原则(非常重要)
使用 指针接收者 的场景:
- 方法需要修改 struct
- struct 较大(避免拷贝)
- struct 将被接口实现
使用 值接收者 的场景:
- struct 很小
- 不涉及修改
- 明确表达“不可变行为”
五、组合(Composition)是 Go 的核心设计方式
1. 为什么 Go 不支持继承
继承的问题:
- 强耦合
- 层级膨胀
- 行为不可控
Go 的解决方案:
组合能力,而不是继承类型
2. 普通组合(has-a)
type Logger struct{}
func (l *Logger) Log(msg string) {
fmt.Println("log:", msg)
}
type UserService struct {
logger *Logger
}调用:
s := UserService{
logger: &Logger{},
}
s.logger.Log("hello")特点:
- 明确依赖关系
- 行为来源清晰
六、嵌入(Embedding):Go 的“能力提升器”
1. 什么是嵌入
type Logger struct{}
func (l *Logger) Log(msg string) {
fmt.Println("log:", msg)
}
type UserService struct {
Logger
}调用:
s := UserService{}
s.Log("hello")说明:
- UserService 并不是 Logger
- 但“表现得像”有 Log 方法
2. 嵌入的本质
嵌入 = 字段 + 方法提升(promotion)
等价于:
type UserService struct {
Logger Logger
}只是语法更简洁。
七、嵌入 struct vs 嵌入接口
1. 嵌入 struct
type Service struct {
Logger
}特点:
- 复用具体实现
- 耦合较强
2. 嵌入接口(更推荐)
type Logger interface {
Log(msg string)
}
type Service struct {
Logger
}优点:
- 面向接口
- 易替换
- 易测试
八、字段与方法冲突(必须知道)
示例:字段覆盖
type A struct{}
func (A) Hello() {
fmt.Println("A")
}
type B struct {
A
}
func (B) Hello() {
fmt.Println("B")
}b := B{}
b.Hello() // B
b.A.Hello() // A结论:
外层优先,内层可显式访问
九、struct 设计的工程原则(非常重要)
1. struct 不要过大
反例:
type User struct {
ID int
Name string
Age int
Address string
OrderCount int
// 一堆无关字段
}建议:
- 拆分领域对象
- 一个 struct 只做一件事
2. 不要把“行为无关的字段”塞进 struct
struct 应该代表:
- 一个明确的概念
- 一组内聚的数据和行为
3. struct 是稳定边界
- struct 变化 = API 变化
- 对外暴露的 struct 要极度谨慎
十、自检问题(进入 Day 5 前必须能回答)
- struct 和 class 的本质区别是什么?
- 为什么 Go 强调组合而不是继承?
- 嵌入到底“多”了什么能力?
- 什么时候该嵌入接口?
- struct 设计中最容易犯的错误是什么?
预告:Day 5
Day 5|interface 设计原则与工程实践
你将真正掌握:
- Go 接口的设计哲学
- 小接口原则
- 面向接口编程的正确姿势
- 为什么“接口是为使用者设计的”