先看不采用继承的情况
代码
package mainimport ("fmt")type Student struct {}// 小学生考试type Pupil struct {Name stringAge intScore float64}func (p *Pupil) ShowInfo() {fmt.Printf("名字:%v, 年龄: %v, 分数:%v \n", p.Name, p.Age, p.Score)}func (p *Pupil) SetScore(score float64) {p.Score = score}func (p *Pupil) testing() {fmt.Println("小学生正在考试")}// 大学生考试type Graduate struct {Name stringAge intScore float64}func (p *Graduate) ShowInfo() {fmt.Printf("名字:%v, 年龄: %v, 分数:%v \n", p.Name, p.Age, p.Score)}func (p *Graduate) SetScore(score float64) {p.Score = score}func (p *Graduate) testing() {fmt.Println("大学生正在考试")}func main() {// 小学生var pupil = &Pupil{Name: "tom",Age: 6,}pupil.testing()pupil.SetScore(99.89)pupil.ShowInfo()// 大学生var graduate = &Graduate{Name: "bill",Age: 19,}graduate.testing()graduate.SetScore(99.89)graduate.ShowInfo()}小学生正在考试名字:tom, 年龄: 6, 分数:99.89大学生正在考试名字:bill, 年龄: 19, 分数:99.89
问题引出
- 上面 Pupil 和 Graduate 结构体的字段和方法一样,但是却写了相同的代码
- 代码冗余,不利于维护,不利于扩展
解决办法——继承
继承——解决代码复用
抽象出一个新的共性的结构体 Student(共同的属性和方法),在这个结构体中定义相同的属性和方法
其它结构体不需要重复定义这些属性和方法,只需要嵌套一个 Student 匿名结构体即可。
在Golang中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的属性和方法,从而实现了继承。
用继承(嵌套匿名结构体)实现
代码
package mainimport ("fmt")type Student struct {Name stringAge intScore float64}func (stu *Student) ShowInfo() {fmt.Printf("名字:%v, 年龄: %v, 分数:%v \n", stu.Name, stu.Age, stu.Score)}func (stu *Student) SetScore(score float64) {stu.Score = score}// 新增方法func (stu *Student) DoHomework() {fmt.Println("学生在做作业")}// 小学生考试type Pupil struct {Student}// 保留 Pupil 特有方法func (p *Pupil) testing() {fmt.Println("小学生正在考试")}// 大学生考试type Graduate struct {Student}// 保留 Graduate 特有方法func (p *Graduate) testing() {fmt.Println("大学生正在考试")}func main() {// 小学生var pupil = &Pupil{}pupil.Name = "tom"pupil.Age = 6pupil.testing()pupil.SetScore(99.89)pupil.ShowInfo()pupil.DoHomework()// 大学生var graduate = &Graduate{}graduate.Name = "bill"graduate.Age = 19graduate.testing()graduate.SetScore(99.89)graduate.ShowInfo()pupil.DoHomework()}
继承使用细节
- 结构体可以使用嵌套匿名结构体的所有字段和方法(不论标识符是大写还是小写)
- 如果内部类型和外部类型有相同的字段或者方法,调用的时候采用就近原则,如果希望访问和设置匿名结构体的字段和方法,可以通过匿名结构体来区分
- 结构体嵌入了两个(或多个)匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段或方法),调用时,必须指定匿名结构体的名字,否则会报错。
- 如果一个 struct 嵌套了有名结构体,这种模式就是组合,如果时组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
- 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
- 多重继承 -> 嵌套多个结构体,同样可以访问字段和方法(为了代码简洁,嵌套关系清晰,建议不要使用多重继承)
package mainimport ("fmt")type A struct {Name stringAge int}type B struct {Name stringAge int}type C struct {AB// Name string}type D struct {a A}type Goods struct {Name stringPrice float64}type Brand struct {Name stringaddr string}// 多重继承type TV struct {GoodsBrand}type TV2 struct {*Goods*Brand}type Monster struct {Name stringAge int}type E struct {Monsterint}func main() {var c C// 如果C没有Name字段,而A和B有Name, 这时就必须通过指定匿名结构体名字来区分// c.Name = "tom" // ambiguous selector c.Name 模糊的选择器c.A.Name = "tom"fmt.Println("c = ", c)//嵌入 有名结构体 => 组合// D 中存在有名结构体,在访问有名结构体的字段或者方法的时候,必须带上有名结构体的名字var d Dd.a.Name = "xixi"fmt.Println("d = ", d)// 字面量形式创建组合实例d2 := D{a: A{Name: "jerry",Age: 2,},}fmt.Println("d2 = ", d2)// 创建 TV 实例tv := TV{Goods{"小米电视", 200}, Brand{"xiaomi", "武汉"}}fmt.Println("tv = ", tv)tv02 := TV{Goods{Name: "华为电视",Price: 220,},Brand{Name: "huawei",addr: "深圳",},}fmt.Println("tv02 = ", tv02)// 内层结构是地址形式时,创建实例tv03 := TV2{&Goods{"熊猫电视", 180}, &Brand{"熊猫", "中国"}}// fmt.Println("tv03 = ", tv03) // {0xc0000040a8 0xc00004e3e0}fmt.Println("tv03 = ", tv03.Goods, tv03.Brand) // &{熊猫电视 180} &{熊猫 中国}fmt.Println("tv03 = ", *tv03.Goods, *tv03.Brand) // {熊猫电视 180} {熊猫 中国}// int 匿名字段是基本数据类型var e Ee.Name = "牛牛"e.Age = 3e.int = 20fmt.Println("e = ", e) // {{牛牛 3} 20}}
