我们先看一个案例
package main
import "fmt"
//比如我们现在有个需求,打印一个小学生的成绩和基本信息,则代码如下
type Pupil struct{
Name string
Age int
Score int
}
//为Pupil绑定3个方法,显示
func (p *Pupil) ShowInfo(){
fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",p.Name,p.Age,p.Score)
}
// 设置成绩
func (p *Pupil) SetScore(score int){
p.Score = score
}
//输出信息
func (p *Pupil) testing(){
fmt.Println("小学生在考试中")
}
func main() {
var pupil = &Pupil{
Name : "tom",
Age : 10,
}
//调用
pupil.testing()
pupil.SetScore(90)
pupil.ShowInfo()
}
如上,我们做了一个小学生的考试信息的程序,拥有结构体和方法
但是,我们后续还可能会有其他的角色,比如我们去添加一个大学生,代码如下
package main
import "fmt"
//小学生
type Pupil struct{
Name string
Age int
Score int
}
//大学生
type Graduate struct{
Name string
Age int
Score int
}
//小学生绑定的方法
func (p *Pupil) ShowInfo(){fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",p.Name,p.Age,p.Score)}
func (p *Pupil) SetScore(score int){p.Score = score}
func (p *Pupil) testing(){fmt.Println("小学生在考试中")}
//大学生绑定的方法
func (p *Graduate) ShowInfo(){fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",p.Name,p.Age,p.Score)}
func (p *Graduate) SetScore(score int){p.Score = score}
func (p *Graduate) testing(){fmt.Println("大学生在考试中")}
func main() {
//测试
var pupil = &Pupil{
Name : "tom",
Age : 10,
}
pupil.testing()
pupil.SetScore(90)
pupil.ShowInfo()
//测试2
var graduate = &Graduate{
Name : "mary",
Age : 20,
}
graduate.testing()
graduate.SetScore(90)
graduate.ShowInfo()
}
我们可以看到,又需要吧上面重复的代码重新写一遍,阅读性差又浪费时间
像是这种情况,我们都是可以使用golang中的继承机制解决
在现实生活中,每个小孩子都会继承父母的一些体貌特征,可能是身高、相貌等等
而在golang中,可以把每个结构体看作是一个小孩子,当多个结构体存在相同的特征(字段)和方法时,我们可以把这些重复出现并且含义相同的字段抽取出来,放到同一个结构体中,当其他结构体需要调用这些字段或方法时,只需要在结构体中添加该结构体的名称即可
这个结构体是通过嵌套在其他结构体中进行调用的,也被称为嵌套匿名结构体
type Goods struct{ //定义匿名结构体
Name string
Price int
}
type Book struct {
Goods //调用嵌套结构体
Writer string
}
package main
import "fmt"
//手动定义一个匿名嵌套结构体,和普通结构体相同
//将小学生和大学生结构体中相同的字段都放在匿名结构体中
type Student struct{
Name string
Age int
Score int
}
//小学生和大学生结构体需要指定要继承的结构体是什么即可
type Pupil struct{
Student //嵌入匿名结构体
}
type Graduate struct{
Student //嵌入匿名结构体
}
//将Pupil 和Graduate 共有的方法 绑定到Student结构体
//方法1 显示成绩
func (stu *Student) ShowInfo(){fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",stu.Name,stu.Age,stu.Score)}
//方法2 设置成绩
func (stu *Student) SetScore(score int){stu.Score = score}
//至于testing方法,他们的结果是不一样的,不能统一合到一起
//保留两个结构体特有的方法
func (p *Pupil) testing(){
fmt.Println("小学生在考试中")
}
func (p *Graduate) testing(){
fmt.Println("大学生在考试中")
}
func main() {
//小学生调用
pupil := &Pupil{} //实例化结构体
pupil.Student.Name = "tom1"
pupil.Student.Age = 8
pupil.testing() //打印考试中
pupil.Student.SetScore(70) //找到匿名结构体下的方法SetScore设置成绩
pupil.Student.ShowInfo() //查看分数
//大学生调用
graduate := &Graduate{}
graduate.Student.Name = "tom2"
graduate.Student.Age = 28
graduate.testing()
graduate.Student.SetScore(90) //找到匿名结构体下的方法
graduate.Student.ShowInfo() //查看分数
}
使用了继承,后续的维护就方便很多了,比如我给所有学生添加一个新的方法计算值
我只需要在匿名结构体这个公共的环境下绑定一个方法即可
func (stu *Student) GetSum(n1 int ,n2 int) int{
return n1 + n2
}
package main
import "fmt"
type A struct {
Name string //字段和方法的大小写,不影响继承
age int
}
func (a *A) SayOK() {
fmt.Println("A SayOk",a.Name)
}
func (a *A) hello() {
fmt.Println("A hello",a.Name)
}
type B struct {
A
}
func main(){
var b B
b.A.Name = "tom"
b.A.age = 19
b.A.SayOK()
b.A.hello()
}
func main(){
var b B
b.A.Name = "tom" //前面我们访问时,是这么调用的
b.A.age = 19 //结构体变量.继承的匿名结构体.方法/字段
b.A.SayOK()
b.A.hello()
}
func main(){
var b B
b.Name = "tom" //其实匿名结构体名称是可以简化的
b.age = 19 //这是因为b结构体是没有这些字段的,程序会先去b找字段
b.SayOK() //如果没有,就会自动去继承的结构体中找字段
b.hello()
}
当你设置的结构体和继承的匿名结构体同时存在相同名称的字段和方法时
编辑器会采取 就近原则 ,优先从结构体中查询,并且如果想指定访问匿名
结构体的字段,需要指定完整的结构体+匿名结构体的名称和字段
package main
import "fmt"
type A struct {
Name string //匿名结构体里我们定义一个Name
age int
}
func (a *A) SayOK() {fmt.Println("A SayOk",a.Name)}
func (a *A) hello() {fmt.Println("A hello",a.Name)}
type B struct {
A
Name string //在b结构体里也定义一个Name
}
func (b *B) SayOK() {
fmt.Println("A SayOk",b.Name)
}
func main(){
var b B
b.Name = "tom" //这里找的是b结构体的name
b.age = 19 //这个是找的b结构体下嵌套的A结构体的 age字段
b.SayOK() //b结构体方法
b.hello() //b结构体下嵌套匿名结构体A的方法
}
我们程序越来越大,可能就会嵌入多个匿名结构体,当有多个匿名结构体内的字段或方法相同时,就必须要指定匿名结构体的名称了,所以一般情况下还是都带上匿名结构体名称吧
package main
import "fmt"
type A struct {
Name string
age int
}
type B struct {
Name string
score string
}
//如上,A、B结构体 字段完全相同
type C struct { //c结构体继承以上的结构体
A
B
}
func main(){
var c C
c.A.Name = "A" //调用就必须指定详细的匿名结构体名称了
c.B.Name = "B"
fmt.Println(c.A.Name)
fmt.Println(c.B.Name)
}
如果一个结构体嵌套了一个有名结构体,这种模式就是组合
如果是组合关系,那么在访问组合的结构体的字段和方式时,必须带上结构体的名称
package main
import "fmt"
type A struct {
Name string
age int
}
type C struct {
a A //在添加有名结构体的时候,可以在前面设置它的别名 这里给A结构体设置别名为a
//这个关系被称为组合关系
}
func main(){
var c C
c.a.Name = "A" //如果c中是有名结构体,则访问有名结构体时,必须携带结构体匿名名称
fmt.Println(c.a.Name)
}
package main
import "fmt"
//嵌套匿名结构体后,也可以在创建结构体变量(实例)直接指定各个匿名结构体字段的值
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
func main(){
//和正常格式化结构体类似,结构体{} 里面包含各个结构体声明
tv := TV{Goods{"电视机001",5000.99},Brand{"海尔","山东青岛"},}
//或者好看点,这么写
tv2 := TV{
Goods{
Name: "电视机001",
Price: 5000.99,
},
Brand{
Name: "海尔",
Address: "山东青岛",
},
}
fmt.Println(tv)
fmt.Println(tv2)
}
package main
import "fmt"
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
*Goods //这里修改为指针模式
*Brand
}
func main(){
//里面两个结构体调用时需要传入内存地址
tv := TV{&Goods{"电视机001",5000.99},&Brand{"海尔","山东青岛"},}
fmt.Println(*tv.Goods) //取指针的值方法
fmt.Println(*tv.Brand)
//或者好看点,这么写
tv2 := TV{
&Goods{
Name: "电视机002",
Price: 7000.99,
},
&Brand{
Name: "海尔3",
Address: "青岛",
},
}
fmt.Println(*tv2.Goods) //取指针的值方法
fmt.Println(*tv2.Brand)
}