Golang语言结构体(struct)面向对象编程进阶篇(封装,继承和多态)
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.Go语言的封装(encapsulation)实现
1.什么是封装(encapsulation)
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起。
数据被保护在内部,程序的其他包只有通过被授权的操作方法,才能对字段进行操作。
2.封装(encapsulation)的好处
- 1.隐藏实现细节;
- 2.可以对数据进行校验,保证安全合理;
3.golang如何实现封装(encapsulation)
- 1.建议将结构体,字段(属性)的首字母小写(其他包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格);
- 2.给结构体所在的包提供一个工程模式函数,首字母大写(类似于一个构造函数);
- 3.提供一个首字母大写的set方法(类似于其他语言的public),用于对属性判断并赋值
- 4.提供一个首字母大写的Get方法(类似于其他语言的publiic),用于获取属性的值;
4.代码实现
4.1 代码组织结构
如上图所示,代码组织结构
4.2 创建go.mod文件
yinzhengjie@bogon 12-encapsulation % go mod init yinzhengjie-fengzhuang
go: creating new go.mod: module yinzhengjie-fengzhuang
yinzhengjie@bogon 12-encapsulation %
yinzhengjie@bogon 12-encapsulation % ls
go.mod
yinzhengjie@bogon 12-encapsulation %
yinzhengjie@bogon 12-encapsulation % cat go.mod
module yinzhengjie-fengzhuang
go 1.22.4
yinzhengjie@bogon 12-encapsulation %
4.3 dongman.go
package dongman
import "fmt"
type dongMan struct {
Name string
// 此处为故意将age,hobby字段设置为小写,这意味着其他包无法直接访问这两个属性。
age int
hobby []string
Leader string
}
func (d dongMan) String() string {
return fmt.Sprintf("[%s]的男主是[%s],在[%d]岁时修炼到元婴,我的爱好是: %s", d.Name, d.Leader, d.age, d.hobby)
}
// 定义工程模式函数,相当于其他Java和Python等编程的构造器
func NewDongMan(name string, age int, leader string, hobby []string) *dongMan {
return &dongMan{
Name: name,
age: age,
Leader: leader,
hobby: hobby,
}
}
// 定义set方法,对age字段进行封装,因为在方法中可以添加一系列的限制操作,确保被封装字段的安全合理性
func (d *dongMan) SetAge(age int) {
// 通过set方法,我们可以设置age的范围,否则外部就可以直接对age字段进行赋值
if age > 0 && age < 1000 {
d.age = age
} else {
fmt.Printf("元婴期修士寿命范围在0~1000岁,您传入的[%d]不合法\n", age)
}
}
// 定义get方法,用于外部包获取隐藏的字段
func (d *dongMan) GetAge() int {
return d.age
}
4.4 main.go
package main
import (
"fmt"
"yinzhengjie-fengzhuang/dongman"
)
func main() {
// 创建dongMan结构体
d := dongman.NewDongMan("《凡人修仙传》", 217, "韩立", []string{"养灵虫", "制傀儡", "炼丹", "修炼", "阵法"})
fmt.Println(d)
// 跨包无法访问小写字母的属性字段
// d.age = 400
// d.hobby = []string{"韩跑跑", "捡破烂"}
// 由于我们将age属性封装起来了,想要访问该字段,则需要通过SetAge方法进行修改,如果字段不合法则不会设置成功哟~
// d.SetAge(2000)
d.SetAge(300)
// 由于我们将age属性封装起来了,想要访问该字段,则需要通过GetAge方法进行查看
fmt.Printf("我是[%s]男主[%s],今年[%d]岁~\n", d.Name, d.Leader, d.GetAge())
}
二.Go语言的继承(inheritance)实现
1.继承(inheritance)概述
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其他的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。
换句话说, 在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,这就是所谓的继承。
如上图所示,继承的优点就是提高了代码的复用性和扩展性,多个结构体无需重复定义属性和方法,仅需关系各自结构体的方法即可。
Golang使用继承注意事项:
- 1.结构体可以使用嵌套匿名结构体所有的字段和方法,包括首字母大写或者小写的字段,方法,都可以使用;
- 2.匿名字段结构体字段访问可以简化;
- 3.当结构体和匿名结构体有相同的字段或者方法时,编译器采用"就近访问"原则访问,如系统访问匿名结构体的字段和方法,可以通过匿名结构体来区分;
- 4.Golang中支持多继承,如一个结构体嵌套了多个匿名结构体,那么该结构体可以访问直接嵌套的你们结构体的字段和方法,从而实现了多重继承;
- 5.如嵌入的匿名结构体有相同的字段名或者方法名称,则在访问时,需要通过匿名结构体类型名来区分;
- 6.结构体的匿名字段可以是基础数据类型,调用时基于该基础数据类型调用即可;
- 7.在创建嵌套匿名结构体变量(实例)时,可以直接指定各个匿名结构体字段的值;
- 8.嵌入匿名结构体的指针也是可以的;
- 9.结构体的字段可以是结构体类型的(组合模式,嵌套结构体),但这种写法并不属于继承关系,只是属于该结构体的一个字段的类型而已;
温馨提示:
为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。
2.继承案例代码实现
package main
import "fmt"
// Animal结构体为表示动物,是其他结构体的"父结构体"
type Animal struct {
Name string
Age int
Weight float64
}
// 给Animal绑定Speark方法
func (a *Animal) Speark() {
fmt.Printf("%s又开始叫唤了...", a.Name)
}
// 给Animal绑定Show方法
func (a *Animal) Show() {
fmt.Printf("%s的今年%d岁,体重是%.2fkg\n", a.Name, a.Age, a.Weight)
}
// Bird结构体表示鸟,属性字段继承自Animal
type Bird struct {
// 为了复用性,体现继承思维,加入匿名结构体
Animal
}
// 为Bird结构体绑定特有的方法
func (b *Bird) Fight() {
fmt.Printf("快看,%s又飞起来啦~\n", b.Name)
}
// Dog结构体表示狗,属性字段继承自Animal
type Dog struct {
Animal
}
// 定义Dog结构体特有的方法
func (d *Dog) Run() {
fmt.Printf("%s狗子,跑的真快\n", d.Name)
}
func (d *Dog) Jump() {
fmt.Printf("%s狗子,跳的好高\n", d.Name)
}
type Cat struct {
Animal
}
func (c *Cat) Scratch() {
fmt.Printf("%s猫咪,又开始抓人了\n", c.Name)
}
func main() {
bird := &Bird{}
bird.Animal.Name = "小黄"
bird.Animal.Age = 1
bird.Animal.Weight = 0.2
dog := &Dog{}
dog.Animal.Name = "雪花"
dog.Animal.Age = 3
dog.Animal.Weight = 4
cat := &Cat{}
cat.Animal.Name = "大花"
cat.Animal.Age = 4
cat.Animal.Weight = 2
bird.Speark()
bird.Show()
bird.Fight()
dog.Speark()
dog.Show()
dog.Run()
dog.Jump()
cat.Speark()
cat.Show()
cat.Scratch()
}
3.Golang支持多继承
package main
import "fmt"
type Father struct {
Name string
Age int
}
type Mother struct {
Name string
}
type Son struct {
Name string
// 结构体的匿名字段可以是基础数据类型,这种没有名字的字段就称为匿名字段,调用时基于该基础数据类型调用即可;
//这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名;
// 结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
int
// 继承多个结构体,尽管Go语言支持多继承,但推荐少用。很多语言就将多重继承去除了,因为容易造成逻辑混乱。
Father
Mother
}
func (f *Father) DriveCar() {
fmt.Printf("%s开车很稳~\n", f.Name)
}
func (m *Mother) Sing() {
fmt.Printf("%s唱歌很好听~\n", m.Name)
}
func (s *Son) Dance() {
fmt.Printf("%s跳舞很好看\n", s.Name)
}
func main() {
// 构建Son结构体实例
// s := Son{"唐三", 18, Father{"唐昊", 30}, Mother{"阿银"}}
s := Son{
"唐三",
18,
// 在创建嵌套匿名结构体变量(实例)时,可以直接指定各个匿名结构体字段的值;
Father{
Name: "唐昊",
Age: 30,
},
Mother{
Name: "阿银",
},
}
fmt.Printf("s = %v\n", s)
// 通过Son结构体实例的确可以调用多个继承结构体的方法
s.Sing()
s.Dance()
s.DriveCar()
// 如嵌入的匿名结构体有相同的字段名或者方法名称,则在访问时,需要通过匿名结构体类型名来区分;
fmt.Printf("[%s]的今年[%d]岁, 父亲是: [%s], 今年[%d]岁, 母亲是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}
4.嵌套匿名结构体指针
package main
import "fmt"
type Father struct {
Name string
Age int
}
type Mother struct {
Name string
}
type Son struct {
Name string
int
// 嵌入匿名结构体的指针也是可以的
*Father
*Mother
}
func (f *Father) DriveCar() {
fmt.Printf("%s开车很稳~\n", f.Name)
}
func (m *Mother) Sing() {
fmt.Printf("%s唱歌很好听~\n", m.Name)
}
func (s *Son) Dance() {
fmt.Printf("%s跳舞很好看\n", s.Name)
}
func main() {
// 构建Son结构体实例
s := Son{"唐三", 18, &Father{"唐昊", 30}, &Mother{"阿银"}}
fmt.Printf("s = %v\n", s)
s.Sing()
s.Dance()
s.DriveCar()
fmt.Printf("[%s]的今年[%d]岁, 父亲是: [%s], 今年[%d]岁, 母亲是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}
5.组合模式(嵌套结构体)并非继承
package main
import (
"fmt"
)
type Address struct {
Province string
City string
}
// 一个结构体中可以嵌套包含另一个结构体或结构体指针,我们也称之为"组合模式"。
type User struct {
Name string
Gender string
// 结构体的字段可以是结构体类型的(组合模式),但这种写法并不属于继承关系,只是属于该结构体的一个字段的类型而已;
Address Address
}
func main() {
user1 := User{
Name: "JasonYin",
Gender: "男",
Address: Address{
Province: "陕西",
City: "安康",
},
}
fmt.Printf("user1=%#v\n", user1)
}
6.嵌套结构体的字段名冲突
package main
import (
"fmt"
)
// Address 地址结构体
type Address struct {
Province string
City string
CreateTime string
}
// Email 邮箱结构体
type Email struct {
Account string
CreateTime string
}
// User 用户结构体
type User struct {
Name string
Gender string
Address
Email
}
func main() {
var u1 User
u1.Name = "Jason Yin"
u1.Gender = "男"
u1.Province = "陕西"
u1.City = "安康"
u1.Account = "y1053419035@qq.com"
// 由于2个匿名字段都有CreateTime字段,因此无法省略匿名字段哟!
// u1.CreateTime = "2020"
// 嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
u1.Address.CreateTime = "2021"
u1.Email.CreateTime = "2025"
fmt.Println(u1)
}
三.Go语言的多态(polymorphic)实现
1.多态概述
变量(实例)具有多种形态,在Go语言中,多态特征是通过接口实现的。可以按照统一的接口来调用不同的接口实现。这时接口变量就呈现出不同的形态。
2.多态案例
package main
import "fmt"
// 定义SayHi接口
type SayHi interface {
SayHello()
}
type Chinese struct {
Name string
}
type American struct {
Name string
}
func (c Chinese) SayHello() {
fmt.Printf("你好,我的名字是: %s, 很高兴认识你。\n", c.Name)
}
func (a American) SayHello() {
fmt.Printf("Hi,My name is %s, And you ?\n", a.Name)
}
// 定义一个函数,专门用来各国人打招呼的函数,接受具备SayHi接口能力的变量
// 此处的s(多态参数)可以通过上下文来识别具体是什么类型的实例,此时就体现出"多态"
func greet(s SayHi) {
s.SayHello()
}
func main() {
// 定义多态数组
var array [3]SayHi
array[0] = Chinese{"女娲"}
array[1] = American{"超人"}
array[2] = Chinese{"夸父"}
fmt.Println(array)
// 遍历接口,调用接口,体现出多态的效果
for _, item := range array {
greet(item)
}
}
四.结构体内存布局
1 结构体占用一块连续的内存
package main
import (
"fmt"
"unsafe"
)
// 结构体占用一块连续的内存。
type test struct {
a int8
b int8
c int8
d int8
}
func main() {
n := test{
11, 22, 33, 44,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
// 查看占用的空间大小
fmt.Println(unsafe.Sizeof(n))
}
2 空结构体
package main
import (
"fmt"
"unsafe"
)
func main() {
// 空结构体是不占用空间的。
var v struct{}
// 查看占用的空间大小
fmt.Println(unsafe.Sizeof(v))
}
3 内存对齐[了解即可]
关于Go语言中的内存对齐推荐阅读:
https://www.liwenzhou.com/posts/Go/struct-memory-layout/
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。
欢迎交流学习技术交流,个人微信: "JasonYin2020"(添加时请备注来源及意图备注)
作者: 尹正杰, 博客: https://www.cnblogs.com/yinzhengjie/p/18331403