Golang中的继承
Golang继承介绍
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在结构体中定义这些相同的属性和方法
在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
下面这个图可以更好的展现Golang语言中的继承关系
Golang中的继承基本语法
使用嵌套匿名结构体
type Goods struct {
Name string
Price int
}
type Book struct {
Goods
Writer string
}
下面我们用继承完成学生考试系统,学生包括小学生和大学生
程序目录为
首先是student.go中的代码
package model
import "fmt"
type Student struct {
Name string
Age int
scores int
}
type Pupil struct {
Student
}
type Graduate struct {
Student
}
func (stu *Student) SetScores(scores int) {
stu.scores = scores
}
func (stu *Student) GetAll() {
fmt.Println("The all various are:", stu.Name, stu.Age, stu.scores)
}
func (pup *Pupil) Testing() {
fmt.Println("The pupil is testing !!!")
}
func (gra *Graduate) Testing() {
fmt.Println("The graduate is testing !!!")
}
再是main.go中的代码
package main
import (
"fmt"
"../model"
)
func main() {
//小学生
pupil := &model.Pupil{}
pupil.Student.Name = "Tom"
pupil.Student.Age = 10
pupil.Testing()
pupil.Student.SetScores(80)
pupil.Student.GetAll()
//大学生
fmt.Println("----------------------------------")
graduate := &model.Pupil{}
graduate.Student.Name = "Bill"
graduate.Student.Age = 21
graduate.Testing()
graduate.Student.SetScores(90)
graduate.Student.GetAll()
}
运行main函数,输出的结果是
The pupil is testing !!!
The all various are: Tom 10 80
-------------------------------
The pupil is testing !!!
The all various are: Bill 21 90有了继承,代码的复用性提高了,代码的扩展性和维护性也提高了
继承的细节讨论
①结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用,当然,这句话的大前提是在一个包的情况下,要是不同包就可能不行了
②匿名结构体字段可以简化
pupil := &model.Pupil{}
pupil.Student.Name = "Tom" -----> pupil.Name = "Tom"
pupil.Student.Age = 10 -----> pupil.Age = 10
pupil.Testing()
pupil.Student.SetScores(80) -----> pupil.SetScores(80)
pupil.Student.GetAll() -----> pupil.GetAll()这里可以这么改,就非常的简单,每次都召唤匿名结构体太复杂了
当我们直接通过pupil访问student方法或字段时,其流程如下,比如pupil.Age,编译器会先看pupil对应的struct有没有Age,如果有就直接调用响应struct类型的Age字段,如果没有就去看pupil嵌入的匿名结构体中有没有声明,如果有就调用,如果没有就继续找student中的匿名结构体,如果也找不到就以此类推继续找,如果都没有找到就报错
③当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
我们写代码来看这个就近原则是怎么个就近原则法,还是用pupil,graduate来说,文件目录和一开始的一样
student.go中的代码
package model
import "fmt"
type Student struct {
Name string
Age int
scores int
}
type Pupil struct {
Student
Name string
}
type Graduate struct {
Student
}
func (stu *Student) SetScores(scores int) {
stu.scores = scores
}
func (stu *Student) GetAll() {
fmt.Println("The all various are:", stu.Name, stu.Age, stu.scores)
}
func (pup *Pupil) GetJustPupil() {
fmt.Println("The all various are:", pup.Name, pup.Age, pup.scores)
}
func (pup *Pupil) Testing() {
fmt.Println("The pupil is testing !!!")
}
func (gra *Graduate) Testing() {
fmt.Println("The graduate is testing !!!")
}
下面是main.go中的代码
package main
import (
"../model"
)
func main() {
//小学生
pupil := &model.Pupil{}
pupil.Name = "Tom"
pupil.Student.Name = "Bill"
pupil.Age = 10
pupil.Testing()
pupil.SetScores(80)
pupil.GetAll()
pupil.GetJustPupil()
}
输出的结果是
The pupil is testing !!!
The all various are: Bill 10 80
The all various are: Tom 10 80我们给pupil.Name赋值的时候,pupil在其结构体内找到了Name,因此就用就近原则直接赋值,如果我们想赋值给匿名结构体中的,就需要指明出来,比如pupil.Student.Name = "Bill",因此接下来pupil.GetAll()绑定的是student结构体指针,编译器在pupil的结构体中找不到GetAll()于是就进入student匿名结构体中寻找方法,找到了之后,匿名结构体中的方法调用的就是匿名结构体中的变量,这也是就近原则。对于pupil.GetJustPupil()方法绑定的是pupil结构体指针,编译器在pupil结构体中寻找到了绑定的GetJustPupil()方法,因此,就直接调用,根据就近原则,与pupil结构体绑定的GetJustPupil()方法就直接调用pupil结构体中的变量
我们再来玩一个,再次修改student.go中的代码
package model
import "fmt"
type Student struct {
Name string
Age int
scores int
}
type Pupil struct {
Student
Name string
}
type Graduate struct {
Student
}
func (stu *Student) SetScores(scores int) {
stu.scores = scores
}
func (stu *Student) GetAll() {
fmt.Println("The all various are:", stu.Name, stu.Age, stu.scores)
}
func (stu *Student) GetName() {
fmt.Println("The name is:", stu.Name)
}
func (pup *Pupil) GetName() {
fmt.Println("The name is:", pup.Name)
}
func (pup *Pupil) GetJustPupil() {
fmt.Println("The all various are:", pup.Name, pup.Age, pup.scores)
}
func (pup *Pupil) Testing() {
fmt.Println("The pupil is testing !!!")
}
func (gra *Graduate) Testing() {
fmt.Println("The graduate is testing !!!")
}
这次在student.go中让父结构体和本结构体都有相同名称的方法
我们在main.go中调用
package main
import (
"../model"
)
func main() {
//小学生
pupil := &model.Pupil{}
pupil.Name = "Tom"
pupil.Student.Name = "Bill"
pupil.Age = 10
pupil.Testing()
pupil.SetScores(80)
pupil.GetAll()
pupil.GetJustPupil()
pupil.GetName()
pupil.Student.GetName()
}
输出的是
The pupil is testing !!!
The all various are: Bill 10 80
The all various are: Tom 10 80
The name is: Tom
The name is: Bill我们看最后两行,第一个是Tom,第二个是Bill
还是一个道理,遵从就近原则
最后再玩一个,student.go中我们将pupil结构体中的Name给注释掉
package model
import "fmt"
type Student struct {
Name string
Age int
scores int
}
type Pupil struct {
Student
// Name string
}
type Graduate struct {
Student
}
func (stu *Student) SetScores(scores int) {
stu.scores = scores
}
func (stu *Student) GetAll() {
fmt.Println("The all various are:", stu.Name, stu.Age, stu.scores)
}
func (stu *Student) GetName() {
fmt.Println("The name is:", stu.Name)
}
func (pup *Pupil) GetName() {
fmt.Println("The name is:", pup.Name)
}
func (pup *Pupil) GetJustPupil() {
fmt.Println("The all various are:", pup.Name, pup.Age, pup.scores)
}
func (pup *Pupil) Testing() {
fmt.Println("The pupil is testing !!!")
}
func (gra *Graduate) Testing() {
fmt.Println("The graduate is testing !!!")
}
我们注释掉,然后main.go中的代码不变,输出如下:
The pupil is testing !!!
The all various are: Bill 10 80
The all various are: Bill 10 80
The name is: Bill
The name is: Bill
我们可以看到两次输出都是Bill。再main函数中,pupil.Name = “Tom”, 由于在pupil结构体中没有找到Name这个字段,因此去匿名结构体中找,找到了就赋值,pupil.Student.Name = "Bill"则是直接去匿名结构体中找,找到了就赋值,因此,“Bill”相当于把“Tom”覆盖掉了
然后接下来的两个方法pupil.GetName()和pupil.Student.GetName()遵从就近原则,在pupil结构体中找不到Name字段,就在其父结构体student中找Name字段,找到了就正常运行
④结构体中嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
为什么一定要标注结构体本身没有同名的字段和方法,因为如果有就不存在这个问题了,直接就近原则在本结构体中找就可以了
type A struct {
Name string
Age int
}
type B struct {
Name string
score int
}
type C struct {
A
B
}
A和B结构体都被嵌入到C中,A和B中都有name,但是C中没有name
在这种情况下,假如C类型的结构体有一个实例变量是c,调用name的时候就要
c.A.Name
c.B.Name
这样才能区分,如果直接用c.Name,编译器直接报错
⑤如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
//这种就是组合
type A struct {
Name string
Age int
}
type C struct {
a A
}
在组合模式下,C结构体中无匿名结构体,这时候要调用A结构体中的Name时,编译器不会自动往上找,只会在C中寻找
⑥嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
pupil1 := &model.Pupil{Student: model.Student{
Name: "zideng",
Age: 12,
Scores: 89},
}
pupil2 := &model.Pupil{Student: model.Student{Name: "yichen", Age: 13, Scores: 90}}
fmt.Println("pupil1 is:", pupil1)
fmt.Println("pupil2 is:", pupil2)
输出的结果是
pupil1 is: &{{zideng 12 89}}
pupil2 is: &{{yichen 13 90}}
还有的人喜欢嵌套的时候直接用结构体指针,比如
type TV struct {
*Goods
*Brand
}
这个在操作的时候,记得使用取地址(&)就可以了
⑦还有一个奇怪的写法,将基本数据类型写成匿名字段
type Monster struct {
Name string
Age int
}
type E struct {
Monster
int //匿名字段可以是基本数据类型,当然这个int匿名字段不能重复
}
func main() {
var e E
e.Name = "猫"
e.Age = 300
e.int = 20
fmt.Println("e=", e)
}
这样用也行
多重继承
如果一个struct嵌套了多个匿名结构体,那么改结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承 这个其实在前面的细节讨论中已经出现过了
type TV struct {
Goods
Brand
}
如果嵌入的匿名结构体有相同的字段名或方法名,则在访问时,需要通过匿名结构体类型名来区分
但是为了保证代码的简洁性,建议尽量不要使用多重继承