Go 入门 06:结构体与方法
Go 入门 06结构体与方法Go 没有 class但通过struct method实现了轻量级 OOP。本篇讲清楚结构体的定义、方法接收者的选择、以及组合优于继承的设计哲学。一、结构体定义typeUserstruct{IDint64NamestringEmailstring}1.1 创建实例// 1. 字段赋值u1:User{ID:1,Name:Tom,Email:tomexample.com}// 2. 顺序赋值不推荐字段顺序变更易出错u2:User{2,Jerry,jerryexample.com}// 3. 零值实例varu3 User// ID0, Name, Email// 4. new 返回指针u4:new(User)u4.NameAlice// 5. 取地址最常用u5:User{Name:Bob}1.2 匿名字段嵌入typeAddressstruct{CitystringCountrystring}typePersonstruct{NamestringAddress// 嵌入结构体不是继承}p:Person{Name:Tom,Address:Address{City:Beijing}}fmt.Println(p.City)// 直接访问等价于 p.Address.City1.3 结构体 tagtag 是结构体字段的元信息常见于序列化场景typeUserstruct{IDint64json:id db:user_idNamestringjson:name validate:requiredEmailstringjson:email,omitempty}二、方法2.1 方法定义方法 函数 接收者typeRectanglestruct{Width,Heightfloat64}// 值接收者func(r Rectangle)Area()float64{returnr.Width*r.Height}// 指针接收者func(r*Rectangle)Scale(factorfloat64){r.Width*factor r.Height*factor}2.2 值接收者 vs 指针接收者维度值接收者指针接收者是否修改原对象否是调用时是否拷贝是结构体大时性能差否接口实现值 / 指针都满足仅指针满足经验法则结构体较大3 个字段或有大字段→ 用指针接收者需要修改字段 → 必须用指针接收者同一类型的方法应保持接收者类型一致避免混用。2.3 方法集typeTstruct{}func(t T)M1(){}// T 和 *T 都有 M1func(t*T)M2(){}// 只有 *T 有 M2这意味着接口断言时如果某接口需要M2那只能用*T满足。三、组合优于继承Go 没有 extends 关键字所有继承都通过嵌入实现typeAnimalstruct{Namestring}func(a Animal)Eat(){fmt.Println(a.Name,is eating)}typeDogstruct{Animal// 嵌入Breedstring}d:Dog{Animal:Animal{Name:Buddy},Breed:Labrador}d.Eat()// 直接调用 Animal 的方法3.1 方法覆盖子结构可以覆盖父结构的方法func(d Dog)Eat(){fmt.Println(d.Name,is eating bones)}调用d.Eat()时会优先匹配 Dog 的方法。仍然可通过d.Animal.Eat()显式调用嵌入类型的方法。3.2 多重嵌入typeWalkerinterface{Walk()}typeSwimmerinterface{Swim()}typeAmphibianstruct{Walker Swimmer}Go 提倡行为通过接口表达结构通过组合复用这就是大名鼎鼎的接口最小化 组合优于继承思想。四、构造函数惯用法Go 没有构造函数关键字约定使用New[Type]函数funcNewUser(name,emailstring)*User{returnUser{ID:nextID(),Name:name,Email:email,}}复杂场景可以使用函数式选项模式Functional OptionstypeServerstruct{hoststringportinttlsbool}typeOptionfunc(*Server)funcWithPort(pint)Option{returnfunc(s*Server){s.portp}}funcWithTLS(enabledbool)Option{returnfunc(s*Server){s.tlsenabled}}funcNewServer(hoststring,opts...Option)*Server{s:Server{host:host,port:80}for_,opt:rangeopts{opt(s)}returns}s:NewServer(localhost,WithPort(8080),WithTLS(true))这是 Go 社区构建复杂对象的事实标准被 gRPC、tRPC-Go 等主流框架广泛采用。五、结构体内存布局字段顺序会影响结构体大小内存对齐typeBadstruct{abool// 1 byte 7 paddingbint64// 8 bytescbool// 1 byte 7 padding}// 总共 24 字节typeGoodstruct{bint64// 8abool// 1cbool// 1 6 padding}// 总共 16 字节结构体字段建议按大小从大到小排列可显著降低内存占用。六、实战用户管理packagemainimportfmttypeUserstruct{IDint64NamestringEmailstring}funcNewUser(name,emailstring)*User{returnUser{Name:name,Email:email}}func(u*User)Rename(newNamestring){u.NamenewName}func(u User)String()string{returnfmt.Sprintf(User%d, %s, %s,u.ID,u.Name,u.Email)}funcmain(){u:NewUser(Tom,tomexample.com)u.Rename(Tommy)fmt.Println(u)}七、小结Go 用 struct method 实现OOP但本质是组合而非继承方法接收者大型结构用指针、小型结构用值函数式选项模式是 Go 构造复杂对象的最佳实践注意结构体内存对齐。下一篇我们将学习 Go 最强大、也是最 Go-idiomatic 的特性接口interface。