Fork me on GitHub

Golang之旅37-继承、封装和多态

面向对象的三大特性

面向对象编程中的三大特性指的是:继承、多态和封装。多态是基于接口实现的。

  • 继承
  • 封装
  • 接口
  • 多态

当我们定义一个结构体的时候,实际上就是把一类事物的共有属性(字段)和行为(方法)提取出来,形成一个物理模型,这种研究问题的方法就是抽象

银行存取款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import "fmt"

// 定义账户结构体
// 账户、密码、余额
type Account struct{
Number string
Pwd string
Balance float64
}

// 存款
func (account *Account) Deposite(money float64, pwd string){
if pwd != account.Pwd{
fmt.Println("输入的密码不正确")
}
if money < 0{
fmt.Println("余额不足")
}
account.Balance += money // 余额+money
fmt.Println("存款成功")
}

// 取款
func (account *Account) WithDraw(money float64, pwd string){
// 下面的步骤有体现封装对数据校验特性
if pwd != account.Pwd{
fmt.Println("输入的密码不正确")
}
if money <= 0 || money > account.Balance{ // 所取的钱不能小于等于0,或者大于余额
fmt.Println("余额不足")
}
account.Balance -= money // 余额 - money
fmt.Println("取款成功")
}

// 查询
func (account *Account) Query(pwd string){
if pwd != account.Pwd{
fmt.Println("输入的密码不正确")
return
}
fmt.Printf("你的账号是=%v 余额=%v \n", account.Number, account.Balance)
}

func main(){
account := &Account{
Number: "gs12345",
Pwd: "666666",
Balance: 100
}

account.Query("666666") //100
account.Deposite(200, "666666")
account.Query("666666") //300
account.WithDraw(150, "666666")
account.Query("666666") //150
}

继承

继承入门

继承可以解决代码复用,当结构体中存在相同的属性和方法时,可以从这些结构体中抽象出结构体,其他的结构体中不需要重新定义这些相同的属性和方法。

如果一个结构体中嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

1
2
3
4
5
6
7
8
9
type Goods struct{
Name string
Price float64
}

type Book struct {
Goods // 嵌套上面的匿名结构体
Writer string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 关于学生的继承栗子

package main
import "fmt"

type Student struct{
Name string
Age int
Score int
}

// 将共有的方法进行绑定
func (stu *Student) ShowInfo(){
fmt.Println(stu.Name, stu.Age, stu,Score)
}
func (stu *Student) SetScore(score int){
stu.Socre = score
}

// 给 *student 增加一个方法
func (stu *Student) GetSum(a, b int) int{
return a + b
}


// 小学生
type Pupil struct{
Student // 匿名结构体
}
// 独有的方法进行保留
func (p *Pupil) testing{
fmt.Println("小学生正在考试...")
}

//大学生
type Graduate struct{
Student // 匿名结构体
}
func (g *Graduate) testing{
fmt.Println("大学生正在考试...")
}

func main(){
// 小学生
pupil := &Pupil{}
pupil.Student.Name = "小明"
pupil.Student.Age = 18
pupil.Student.SetScore(70)
pup.Student.ShowIfno()
fmt.Println("res=", pupil.Student.GetSum(1, 2))

// 大学生
graduate := &Graduate{}
pupil.Student.Name = "小红"
pupil.Student.Age = 24
pupil.Student.SetScore(89)
pup.Student.ShowIfno()
fmt.Println("res=", graduate.Student.GetSum(1, 2))
}
继承深入
  1. 结构体可以使用匿名结构体的所有字段,包含大写和小写都可以
  2. 匿名结构体字段访问可以简化
  3. 如果结构体和匿名结构体中含有相同字段,编译器采用的是就近访问原则;如果需要希望访问匿名结构体的字段和方法,可以通过匿名结构体名来进行区分
  4. 结构体中嵌入两个或者多个匿名结构体,如果两个结构体中有相同的字段或者方法(同时结构体本身没有同名的字段或者方法),在访问的时候,必须指明匿名结构体名字,否则编译报错。
  5. 如果结构体中嵌套了有名结构体,这种模式就是组合,此时访问结构体中的字段或者属性,必须带上结构体的名字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
Name string
}

func (b *B) Sayok(){
fmt.Println("B Sayok()", b.Name)
}

type C struct{
A
B
// Name string
}

type D struct{
a A // 有名结构体
Name string
}

func main(){
var b B
b.A.Name = "tom"
b.A.age = 18
b.A.Sayok() // 大小写均可访问
b.A.hello()

// 第二点:上面的简化写法
b.Name = "smith"
b.A.Name = "jackson"
b.age = 27
b.Sayok() // 第三点:访问的是本身的Sayok()方法
b.hello() // A Sayok jackson

var c C
c.A.Name = "john"
fmt.Println("c")

var d D
d.a.Name = "mike" // 如果D中没有,则必须带上结构体的名字
d.Name = "jack" // 先找D本身中有没有 Name 字段
}

封装encapsulation

把抽象的字段和对字段的操作封装在一起,数据被保护在内部。程序的其他包只能通过被授权的方式才能对其进行操作。电视机的操作就是典型的封装

  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理
  • 对结构体的属性进行封装
  • 通过方法和包等实现封装

MwZ7fx.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// model/person.go

package model

import "fmt"

type person struct{
Name string
age int // 不可导出的字段
sal float64
}

// 写一个工厂函数,类似构造函数
func NewPerson(name string) *person{
return &person{
Name: name,
}
}

// 访问age和sal
func (p *person) SetAge(age int){
if age > 0 && age < 150{
p.age = age
}else {
fmt.Println("年龄范围不对")
}
}

func (p *person) GetAge() int{
return p.age
}


// 对薪水的操作
func (p *person) SetSal(sal float64){
if sal > 3000 && sal < 30000{
p.sal = sal
}else {
fmt.Println("薪水范围不对")
}
}

func (p *person) GetSal() float64{
return p.sal
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// main/main.go

package main

import (
"code/char27-encapsulation/model"
"fmt"
)

func main(){
p := model.NewPerson("xiaoming")

p.SetAge(18)
p.SetSal(5000)
fmt.Println(p) // xiaoming 0 0

fmt.Println(p.Name, "age=", p.GetAge(), "sal=", p.GetSal())
}

接口interface

简介

接口是一种抽象的类型,基于方法实现的。interface是一组method的集合。接口做的事情就像是定义了一个协议(规则)。高内聚低耦合特性。接口是引用类型

Go语言提倡面向接口编程。每个接口由数个方法组成,接口的定义格式如下:

1
2
3
4
5
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}

其中:

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

举个例子:

1
2
3
type writer interface{
Write([]byte) error
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main
import "fmt"

// 定义接口
// 在接口中定义方法
type Usber interface{
Start()
Stop()
}

// 定义两个结构体
type Phone struct{}

type Camera struct{}

// 给 phone 实现接口中全部的方法
func (p Phone) Start(){
fmt.Println("手机开始工作")
}
func (p Phone) Stop(){
fmt.Println("手机停止工作")
}

// 给 camera 实现接口中全部的方法
func (c Camera) Start(){
fmt.Println("相机开始工作")
}
func (c Camera) Stop(){
fmt.Println("相机停止工作")
}

// computer 结构体
type Computer struct{}

// 只要实现了接口,就实现了该接口中声明的全部方法
func (c Computer) Working(usb Usber){
// 通过接口变量 usb 来调用接口 Usber 中的方法
usb.Start()
usb.Stop()
}

func main(){
// 创建结构体变量
computer := Computer{}
phone := Phone{}
camera := Camere{}

// usb随着传入的变量不同,执行不同的工作,体现多态
computer.Working(phone)
computer.Working(camera)
}
注意事项和细节
  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
  2. 一个自定义类型需要实现接口中全部的方法
  3. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
  4. 一个自定义类型可以实现多个接口
  5. 接口之间可以存在继承关系
  6. interface默认是一个指针(引用类型)
  7. 空接口没有任何方法,所有类型都实现了空接口,可以把任何一个变量赋值给空接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
import "fmt"

type Writer interface{
Say()
}

type Stu struct{
Name string
}

func (stu Stu) Say(){
fmt.Println("Stu Say()")
}

type integer int

func(i integer) Writer(){
fmt.Println("integer Writer i=", i)
}

type T interface {}

func main(){
var stu Stu // 结构体实现了接口的方法
var a Writer = stu
a.Say()

var i integer = 10
var b Writer = i
b.Say() // integer Say i = 10

// 空接口使用
var t T = stu // 空接口
fmt.Println(t)
var t2 inteface{} = stu // 空接口
var num1 float64 = 8.8
t2 = num1
t = num1
fmt.Println(t2, t)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "fmt"

type Usb interface{
Say()
}

type Stu struct{}

func (this *Stu) Say(){ // 用指针实现Say()方法
fmt.Println("Say()")
}

func main(){
var stu Stu = Stu{}
// Stu 类型没有实现 Usb 接口
var u Usb = &stu // 带上&
u.Say()
fmt.Println(u)
}
结构体切片排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import (
"fmt"
"sort"
"math/rand"
)

// 1. 声明 Hero 结构体
type Hero struct{
Name string
Age int
}

// 2. 声明一个Hero结构体切片类型
type HeroSlice []Hero

// 3. 实现 Interface 接口:文档中有三个方法都需要实现
func (hs HeroSlice) Len() int{
return len(hs)
}

// 决定是使用哪种方法进行排序
func (hs HeroSlice) Less(i, j int) bool{
return hs[i].Age < hs[j].Age
}

func (hs HeroSlice) Swap(i, j int){
//temp := hs[i]
//hs[i] = hs[j]
//hs[j] = temp
hs[i], hs[j] = hs[j], hs[i]
}

func main(){
var intSlice = []int{0,-1,9,4,2,10}
// 冒泡排序
// 内置的sort函数
sort.Ints(intSlice)
fmt.Println(intSlice)

// 结构体切片进行排序
var heros HeroSlice
for i := 0;i < 10;i++{
hero := Hero{
Name: fmt.Sprintf("英雄~%d", rand.Intn(100)),
Age: rand.Intn(100), // 如何生成随机数
}
// 将hero append到heros 切片中
heros = append(heros, hero)
}
// 排序前
for _, v := range heros {
fmt.Println(v)
}
// 排序后
// 调用sort.Sort
sort.Sort(heros)
}

接口和继承关系

当结构体需要扩展功能,同时不破坏继承关系,可以使用接口去实现。接口是对继承的一个补充

继承:解决代码的复用性和可维护性

接口:设计好各种规范,让其他自定义类型去实现这些方法;接口在一定程度上能实现代码解藕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main
import "fmt"

type Monkey struct{
Name string
}

func (this *Monkey) climbing(){
fmt.Println(this.Name, "生来会爬树")
}

// 声明一个接口
type BirdAble interface{
Flying()
}

type FishAble interface{
Swimming()
}

func LittleMonkey struct{
Monkey // 匿名结构体,继承
}

func (this *LittleMonkey) Flying(){
fmt.Println(this.Name, "通过学习,会飞翔")
}

func (this *LittleMonkey) Swimming(){
fmt.Println(this.Name, "通过学习,会游泳")
}

func main(){
// 创建一个实例
monkey := LittleMonkey{
Monkey{
Name: "悟空",
},
}
monkey.climbing()
monkey.Flying()
monkey.Swimming()
}

多态poly

变量具有多种形态,多态特征是通过接口来实现的

  • 多态参数:usb就是多态参数
  • 多态数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import "fmt"

// 定义接口
// 在接口中定义方法
type Usber interface{
Start()
Stop()
}

// 定义两个结构体
type Phone struct{}

type Camera struct{}

// 给 phone 实现接口中全部的方法
func (p Phone) Start(){
fmt.Println("手机开始工作")
}
func (p Phone) Stop(){
fmt.Println("手机停止工作")
}

// 给 camera 实现接口中全部的方法
func (c Camera) Start(){
fmt.Println("相机开始工作")
}
func (c Camera) Stop(){
fmt.Println("相机停止工作")
}

// computer 结构体
type Computer struct{}

// 只要实现了接口,就实现了该接口中声明的全部方法
func (c Computer) Working(usb Usber){
// 通过接口变量 usb 来调用接口 Usber 中的方法
usb.Start()
usb.Stop()
}

func main(){
// 创建结构体变量
computer := Computer{}
phone := Phone{}
camera := Camere{}

// usb随着传入的变量不同,执行不同的工作,体现多态
computer.Working(phone)
computer.Working(camera)

// 多态数组
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"}

}

本文标题:Golang之旅37-继承、封装和多态

发布时间:2019年11月16日 - 19:11

原始链接:http://www.renpeter.cn/2019/11/16/Golang%E4%B9%8B%E6%97%8537-%E7%BB%A7%E6%89%BF%E3%80%81%E5%B0%81%E8%A3%85%E5%92%8C%E5%A4%9A%E6%80%81.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea