面向对象编程三大特征2
面向对象编程三大特性--继承
为什么需要继承:
一个小问题,看个学生考试系统的程序extends01.go,提出代码复用的问题:
代码:
package main
import (
	  "fmt"
)
//编写一个学生考试系统
//小学生
type Pupil 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) 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 \n", 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 \n", 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)
}
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号