面向对象编程三大特征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)
}

posted @ 2019-08-25 16:25  我是一只忙碌的小青蛙  阅读(264)  评论(0编辑  收藏  举报