面向对象编程三大特性--继承
为什么需要继承:
一个小问题,看个学生考试系统的程序extends01.go,提出代码复用的问题:
代码:
package main
import (
"fmt"
)
//编写一个学生考试系统
//小学生
type Pupil struct {
Name string
Age int
Score int
}
//显示他的成绩
func (p *Pupil) ShowInfo() {
fmt.Printf("学生名=%v 年龄=%v 成绩=%v
", p.Name, p.Age, p.Score)
}
func (p *Pupil) SetScoure(score int) {
if score < 0 || score > 100 {
fmt.Println("您输入的成绩不正确")
}
p.Score = score
}
func (p *Pupil) testing() {
fmt.Println("小学生正在考试中...")
}
//大学生
type Graduate struct {
Name string
Age int
Score int
}
//显示他的成绩
func (p *Graduate) ShowInfo() {
fmt.Printf("学生名=%v 年龄=%v 成绩=%v
", p.Name, p.Age, p.Score)
}
func (p *Graduate) SetScoure(score int) {
if score < 0 || score > 100 {
fmt.Println("您输入的成绩不正确")
}
p.Score = score
}
func (p *Graduate) testing() {
fmt.Println("大学生正在考试中...")
}
//代码冗余...高中生....
func main() {
var pupil = &Pupil{
Name : "tom",
Age : 10,
}
pupil.testing()
pupil.SetScoure(90)
pupil.ShowInfo()
var graudate = &Graduate{
Name : "mary",
Age : 20,
}
graudate.testing()
graudate.SetScoure(80)
graudate.ShowInfo()
}
对上面代码的小结:
1)Pupil 和 Graduate 两个结构体的字段和方法几乎一样,但是我们却写了两份几乎相同的代码,代码复用性不强。
2)出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
3)解决方法--通过继承方式来解决
继承基本介绍和示意图:
继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段) 和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可。
示意图:
也就是说:在Golang中,如果一个struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
嵌套匿名结构体的基本语法:
type Goods struct {
Name string
Price int
}
type Book struct {
Goods //这里就是嵌套匿名结构体Goods
Writer string
}
快速入门:
我们对extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处
代码实现:
package main
import (
"fmt"
)
//编写一个学生考试系统
type Student struct {
Name string
Age int
Score int
}
//将Pupil 和 Graduate 共有的方法也绑定到 *Student
func (stu *Student) ShowInfo() {
fmt.Printf("学生名=%v 年龄=%v 成绩=%v
", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
if score < 0 || score > 100 {
fmt.Println("您输入的成绩不正确")
}
stu.Score = score
}
//给 *Student 增加一个方法,那么Pupil 和 Graduate 都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int {
return n1 + n2
}
//小学生
type Pupil struct {
Student
}
//这是Pupil结构体特有的方法,保留
func (p *Pupil) testing() {
fmt.Println("小学生正在考试中...")
}
//大学生
type Graduate struct {
Student
}
//这是Graduate结构体特有的方法,保留
func (p *Graduate) testing() {
fmt.Println("大学生正在考试中...")
}
func main() {
//当我们队结构体嵌入了你们结构体后,使用的方法会发生变化。
pupil := &Pupil{}
pupil.Student.Name = "tom~"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(70)
pupil.Student.ShowInfo()
fmt.Println("res=",pupil.Student.GetSum(1,2))
graduate := &Graduate{}
graduate.Student.Name = "mary"
graduate.Student.Age = 20
graduate.testing()
graduate.Student.SetScore(90)
graduate.Student.ShowInfo()
fmt.Println("res2=", graduate.Student.GetSum(3,4))
}
继承的深入讨论:
1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
案例:
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)
}
func (a *A) hello2() {
fmt.Println("A hello", a.age)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "tom"
b.A.age = 19
b.A.Sayok()
b.A.hello()
b.A.hello2()
}
2)匿名结构体字段访问可以简化
案例:
func main() {
var b B
b.A.Name = "tom"
b.A.age = 19
b.A.Sayok()
b.A.hello()
b.A.hello2()
//上面的写法可以简化
b.Name = "smith"
b.age = 20
b.Sayok()
b.hello()
b.hello2()
}
对上面的代码小结:
(1)当我们直接通过b 访问字段或方法时,执行流程如下:比如 b.Name
(2)编译器会先看 b 对应的类型有没有Name,如果有,则直接调用 B 类型的 Name 字段
(3)如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找...如果都找不到就报错。
3)当结构体和匿名结构体有相同的字段或方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
案例:
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)
}
func (a *A) hello2() {
fmt.Println("A hello", a.age)
}
type B struct {
A
Name string
}
func main() {
var b B
b.Name = "jack"
b.A.Name = "soctt"
b.age = 100
b.Sayok()
b.hello()
}
4)结构体嵌入两个(或多个) 匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
案例:
type A struct {
Name string
age int
}
type B struct {
Name string
score float64
}
type C struct {
A
B
}
func main() {
var c C
//如果c 没有Name字段,而A 和 B有Name字段,这时必须指定匿名结构体名字
//所以 c.Name 就会报编译错误,这个规则对方法也是一样的
//c.Name = "tom" //error
c.A.Name = "tom" //OK
fmt.Println(c) //{{tom 0}{ 0}}
}
5)如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
案例:
type A struct {
Name string
age int
}
type D struct {
a A //嵌套了有名结构体
}
func main() {
//如果D 中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字
//比如 d.a.Name
var d D
d.a.Name = "jack"
fmt.Println(d)
}
6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
案例:
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct {
*Goods
*Brand
}
func main() {
tv1 := TV {Goods{"电视机001", 5000},Brand{"海尔", "山东青岛"},}
tv2 := TV {
Goods{"电视机002", 5000.99},
Brand{"西门子", "北京"},
}
tv3 := TV {
Goods{
Price : 4000,
Name : "电视机003",
},
Brand{
Name : "夏普",
Address : "上海",
},
}
fmt.Println(tv1)
fmt.Println(tv2)
fmt.Println(tv3)
tv4 := TV2 {&Goods{"电视机004", 7000}, &Brand{"创维", "河南"},}
tv5 := TV2 {
&Goods{
Name : "电视机005",
Price: 6000,
},
&Brand{
Name : "飞利浦",
Address : "荷兰",
},
}
fmt.Println(*tv4.Goods, *tv4.Brand)
fmt.Println(*tv5.Goods, *tv5.Brand)
}