作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
目录
- 一.Go语言的封装(encapsulation)实现
- 1.什么是封装(encapsulation)
- 2.封装(encapsulation)的好处
- 3.golang如何实现封装(encapsulation)
- 4.代码实现
- 4.1 代码组织结构
- 4.2 创建go.mod文件
- 4.3 dongman.go
- 4.4 main.go
- 二.Go语言的继承(inheritance)实现
- 1.继承(inheritance)概述
- 2.继承案例代码实现
- 3.Golang支持多继承
- 4.嵌套匿名结构体指针
- 5.组合模式(嵌套结构体)并非继承
- 6.嵌套结构体的字段名冲突
- 三.Go语言的多态(polymorphic)实现
- 1.多态概述
- 2.多态案例
- 四.结构体内存布局
- 1 结构体占用一块连续的内存
- 2 空结构体
- 3 内存对齐[了解即可]
一.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-fengzhuanggo 1.22.4
yinzhengjie@bogon 12-encapsulation %
4.3 dongman.go
package dongmanimport "fmt"type dongMan struct {Name string// 此处为故意将age,hobby字段设置为小写,这意味着其他包无法直接访问这两个属性。age inthobby []stringLeader 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 mainimport ("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 mainimport "fmt"// Animal结构体为表示动物,是其他结构体的"父结构体"
type Animal struct {Name stringAge intWeight 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 = 1bird.Animal.Weight = 0.2dog := &Dog{}dog.Animal.Name = "雪花"dog.Animal.Age = 3dog.Animal.Weight = 4cat := &Cat{}cat.Animal.Name = "大花"cat.Animal.Age = 4cat.Animal.Weight = 2bird.Speark()bird.Show()bird.Fight()dog.Speark()dog.Show()dog.Run()dog.Jump()cat.Speark()cat.Show()cat.Scratch()}
3.Golang支持多继承
package mainimport "fmt"type Father struct {Name stringAge int
}type Mother struct {Name string
}type Son struct {Name string// 结构体的匿名字段可以是基础数据类型,这种没有名字的字段就称为匿名字段,调用时基于该基础数据类型调用即可;//这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名;// 结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。int// 继承多个结构体,尽管Go语言支持多继承,但推荐少用。很多语言就将多重继承去除了,因为容易造成逻辑混乱。FatherMother
}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 mainimport "fmt"type Father struct {Name stringAge int
}type Mother struct {Name string
}type Son struct {Name stringint// 嵌入匿名结构体的指针也是可以的*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 mainimport ("fmt"
)type Address struct {Province stringCity string
}// 一个结构体中可以嵌套包含另一个结构体或结构体指针,我们也称之为"组合模式"。
type User struct {Name stringGender string// 结构体的字段可以是结构体类型的(组合模式),但这种写法并不属于继承关系,只是属于该结构体的一个字段的类型而已;Address Address
}func main() {user1 := User{Name: "JasonYin",Gender: "男",Address: Address{Province: "陕西",City: "安康",},}fmt.Printf("user1=%#v\n", user1)
}
6.嵌套结构体的字段名冲突
package mainimport ("fmt"
)// Address 地址结构体
type Address struct {Province stringCity stringCreateTime string
}// Email 邮箱结构体
type Email struct {Account stringCreateTime string
}// User 用户结构体
type User struct {Name stringGender stringAddressEmail
}func main() {var u1 Useru1.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 mainimport "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]SayHiarray[0] = Chinese{"女娲"}array[1] = American{"超人"}array[2] = Chinese{"夸父"}fmt.Println(array)// 遍历接口,调用接口,体现出多态的效果for _, item := range array {greet(item)}
}
四.结构体内存布局
1 结构体占用一块连续的内存
package mainimport ("fmt""unsafe"
)// 结构体占用一块连续的内存。
type test struct {a int8b int8c int8d 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 mainimport ("fmt""unsafe"
)func main() {// 空结构体是不占用空间的。var v struct{}// 查看占用的空间大小fmt.Println(unsafe.Sizeof(v))
}
3 内存对齐[了解即可]
关于Go语言中的内存对齐推荐阅读:https://www.liwenzhou.com/posts/Go/struct-memory-layout/