扫码订阅《 Go语言核心编程教程》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 尚硅谷Go语言核心编程教程

    • 课程说明
    • 第1章 GOLANG 开山篇
    • 第2章 GOLANG 的概述
    • 第3章 GOLANG 变量
    • 第4章 运算符
    • 第5章 程序流程控制
    • 第6章 函数、包和错误处理
    • 第7章 数组与切片
    • 第8章 排序和查找
    • 第9章 map
    • 第10章 面向对象编程 ( 上 )
    • 第11章 面向对象编程 ( 下 )
    • 第12章 项目1:家庭收支记账软件项目
    • 第13章 项目2:客户信息关系系统
    • 第14章 文件操作
    • 第15章 单元测试
    • 第16章 goroutine和channel
    • 第17章 反射
    • 第18章 TCP 编程
    • 第19章 REDIS 的使用
    • 第20章 数据结构

扫码订阅《 Go语言核心编程教程》或入驻星球,即可阅读文章!

第11章 面向对象编程 ( 下 )


GOLANG ROADMAP

# 11.1 VSCODE的使用

【点击观看视频】VSCode配置和快捷键

# 11.1.1 VSCode使用技巧和经验

  • 设置字体 : 文件->首选项->设置

image-20210111155827771

  • 快捷键的使用:自定义快捷配置:文件->首选项->键盘快捷方式

    image-20210111155956753

  • 介绍几个常用的快捷键

    image-20210111160234464

# 11.2 面向对象编程思想-抽象

【点击观看视频】面向对象编程思想-抽象

# 11.2.1 抽象的介绍

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

image-20210111161128175

# 11.2.2 代码实现

package main

import (
	"fmt"
)
//定义一个结构体Account
type Account struct {
	AccountNo string
	Pwd string
	Balance float64
}

//方法
//1. 存款
func (account *Account) Deposite(money float64, pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	//看看存款金额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return 
	}

	account.Balance += money
	fmt.Println("存款成功~~")

}

//取款
func (account *Account) WithDraw(money float64, pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	//看看取款金额是否正确
	if money <= 0  || money > account.Balance {
		fmt.Println("你输入的金额不正确")
		return 
	}

	account.Balance -= money
	fmt.Println("取款成功~~")

}

//查询余额
func (account *Account) Query(pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	fmt.Printf("你的账号为=%v 余额=%v \n", account.AccountNo, account.Balance)

}


func main() {

	//测试一把
	account := Account{
		AccountNo : "gs1111111",
		Pwd : "666666",
		Balance : 100.0,
	}

	//这里可以做的更加灵活,就是让用户通过控制台来输入命令...
	//菜单....
	account.Query("666666")
	account.Deposite(200.0, "666666")
	account.Query("666666")
	account.WithDraw(150.0, "666666")
	account.Query("666666")
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  • 对上面代码的要求

    1 ) 同学们自己可以独立完成 2 ) 增加一个控制台的菜单,可以让用户动态的输入命令和选项

# 11.3 面向对象编程三大特性-封装

【点击观看视频】面向对象编程-封装介绍

# 11.3.1 基本介绍

Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,下面我们一一为同学们进行详细的讲解Golang的三大特性是如何实现的。

# 11.3.2 封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作

image-20210111162005933

# 11.3.3 封装的理解和好处

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理(Age)

# 11.3.4 如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包 实现封装

# 11.3.5 封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)

  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数

  3. 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值

    func(var 结构体类型名)SetXxx(参数列表)(返回值列表){
        //加入数据验证的业务逻辑
        var.字段 =参数
    }
    
    1
    2
    3
    4
  4. 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值

    func(var 结构体类型名)GetXxx(){
    	returnvar.age;
    }
    
    1
    2
    3

特别说明:在Golang开发中并没有特别强调封装,这点并不像Java. 所以提醒学过java 的朋友,不用总是用java的语法特性来看待Golang,Golang本身对面向对象的特性做了简化的.

# 11.3.6 快速入门案例

【点击观看视频】封装快速入门案例

看一个案例

请大家看一个程序(person.go),不能随便查看人的年龄 , 工资等隐私,并对输入的年龄进行合理的验证。设计:model包(person.go)main包(main.go 调用Person结构体)

代码实现 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 我们编写一对SetXxx的方法和GetXxx的方法
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
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

main/main.go

package main
import (
	"fmt"
	"go_code/chapter11/encapsulate/model"
)

func main() {

	p := model.NewPerson("smith")
	p.SetAge(18)
	p.SetSal(5000)
	fmt.Println(p)
	fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal())
	
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 11.3.7 课堂练习(学员先做)

【点击观看视频】封装课堂练习讲解

要求

  1. 创建程序,在model包中定义Account结构体:在main函数中体会Golang的封装性。
  2. Account结构体要求具有字段:账号(长度在 6 - 10 之间)、余额(必须> 20 )、密码(必须是六
  3. 通过SetXxx的方法给Account 的字段赋值。(同学们自己完成)
  4. 在main函数中测试

代码实现

model/account.go

package model

import (
	"fmt"
)
//定义一个结构体account
type account struct {
	accountNo string
	pwd string
	balance float64
}

//工厂模式的函数-构造函数
func NewAccount(accountNo string, pwd string, balance float64) *account {

	if len(accountNo) < 6 || len(accountNo) > 10 {
		fmt.Println("账号的长度不对...")
		return nil
	}

	if len(pwd) != 6 {
		fmt.Println("密码的长度不对...")
		return nil
	}

	if balance < 20 {
		fmt.Println("余额数目不对...")
		return nil
	}

	return &account{
		accountNo : accountNo,
		pwd : pwd,
		balance : balance,
	}

}

//方法
//1. 存款
func (account *account) Deposite(money float64, pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	//看看存款金额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return 
	}

	account.balance += money
	fmt.Println("存款成功~~")

}

//取款
func (account *account) WithDraw(money float64, pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	//看看取款金额是否正确
	if money <= 0  || money > account.balance {
		fmt.Println("你输入的金额不正确")
		return 
	}

	account.balance -= money
	fmt.Println("取款成功~~")

}

//查询余额
func (account *account) Query(pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	fmt.Printf("你的账号为=%v 余额=%v \n", account.accountNo, account.balance)

}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

main/main.go

package main

import (
	"fmt"
	"go_code/chapter11/encapexercise/model"
)

func main() {
	//创建一个account变量
	account := model.NewAccount("jzh11111", "000", 40)
	if account != nil {
		fmt.Println("创建成功=", account)
	} else {
		fmt.Println("创建失败")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

说明:在老师的代码基础上增加如下功能:

通过SetXxx的方法给Account 的字段赋值 通过GetXxx方法获取字段的值。(同学们自己完成)在main函数中测试

# 11.4 面向对象编程三大特性-继承

【点击观看视频】面向对象编程-继承引出

# 11.4.1 看一个问题,引出继承的必要性

一个小问题,看个学生考试系统的程序extends 01 .go,提出代码复用的问题

image-20210111163628810

走一下代码

package main

import(
"fmt"
)

//编写一个学生考试系统

//小学生
type Pupilstruct{
    Namestring
    Ageint
    Scoreint
}

//显示他的成绩
func(p *Pupil)ShowInfo(){
	fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",p.Name,p.Age,p.Score)
}

func(p *Pupil)SetScore(scoreint){
    //业务判断
    p.Score=score
}

func(p*Pupil)testing(){
	fmt.Println("小学生正在考试中.....")
}

//大学生, 研究生。。

//大学生
type Graduatestruct{
    Namestring
    Ageint
    Scoreint
}

//显示他的成绩
func(p *Graduate)ShowInfo(){
	fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n",p.Name,p.Age,p.Score)
}

func(p *Graduate)SetScore(scoreint){
    //业务判断
    p.Score=score
}

func(p *Graduate)testing(){
	fmt.Println("大学生正在考试中.....")
}

//代码冗余.. 高中生....

func main(){
    //测试
    varpupil=&Pupil{
        Name:"tom",
        Age: 10 ,
    }
    pupil.testing()
    pupil.SetScore( 90 )
    pupil.ShowInfo()
    
    //测试
    vargraduate=&Graduate{
        Name:"mary",
        Age: 20 ,
    }
    graduate.testing()
    graduate.SetScore( 90 )
    graduate.ShowInfo()
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73

对上面代码的小结

  1. Pupil 和 Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码,代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
  3. 解决方法-通过继承方式来解决

# 11.4.2 继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可。[画出示意图 ]

image-20210111164814267

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

# 11.4.3 嵌套匿名结构体的基本语法

【点击观看视频】继承基本语法
type Goodsstruct{
    Namestring
    Priceint
}
type Bookstruct{
    Goods //这里就是嵌套匿名结构体Goods
    Writerstring
}
1
2
3
4
5
6
7
8

# 11.4.4 快速入门案例

【点击观看视频】继承快速入门应用实例

案例

我们对extends 01 .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) {
	//业务判断
	stu.Score = score
}

//给 *Student 增加一个方法,那么 Pupil 和 Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int {
	return n1 + n2
}

//小学生
type Pupil struct { 
	Student //嵌入了Student匿名结构体
}

//显示他的成绩

//这时Pupil结构体特有的方法,保留
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中.....")
}

//大学生, 研究生。。


//大学生
type Graduate struct {
	Student //嵌入了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 = 28
	graduate.testing() 
	graduate.Student.SetScore(90)
	graduate.Student.ShowInfo()
	fmt.Println("res=", graduate.Student.GetSum(10, 20))
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

# 11.4.5 继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

# 11.4.6 继承的深入讨论

【点击观看视频】继承的深入讨论(1)
【点击观看视频】继承的深入讨论(2)

1 ) 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用

【举例说明】

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)
}

func main() {
	 var b B
	 b.A.Name = "tom"
	 b.A.age = 19
	 b.A.SayOk()
	 b.A.hello()
}
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

2 ) 匿名结构体字段访问可以简化

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)
}

func main() {

	 b.Name = "smith"
	 b.age = 20
	 b.SayOk()
	 b.hello()

}
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

对上面的代码小结

  1. 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name

  2. 编译器会先看b对应的类型有没有Name, 如果有,则直接调用B类型的Name字段

  3. 如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有继续查找..如果都找不到就报错.

  4. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】

   func main() {
   	var b B
   	b.Name = "jack" // ok
   	b.A.Name = "scott"
   	b.age = 100  //ok
   	b.SayOk()  // B SayOk  jack
   	b.A.SayOk() //  A SayOk scott
   	b.hello() //  A hello ? "jack" 还是 "scott"
   }
1
2
3
4
5
6
7
8
9
  1. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】

5 ) 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

6 ) 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

# 11.4.7 课堂练习

结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么

说明

1 ) 如果一个结构体有int类型的匿名字段,就不能第二个。

2 ) 如果需要有多个int的字段,则必须给int字段指定名字

# 11.4.8 面向对象编程-多重继承

【点击观看视频】多重继承介绍
  • 多重继承说明

如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

  • 案例演示 通过一个案例来说明多重继承使用

image-20210115224909098

  • 多重继承细节说明

1 ) 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。【案例演示】

image-20210115224922874

2 ) 为了保证代码的简洁性,建议大家尽量不使用多重继承

# 11.5 接口(INTERFACE)

【点击观看视频】接口介绍和快速入门

# 11.5.1 基本介绍

按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在Golang中 多态 特性主要是通过接口来体现的。

# 11.5.2 为什么有接口

image-20210115224944306

# 11.5.3 接口快速入门

这样的设计需求在Golang编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世 界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。

  • 代码实现
package main
import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
	//声明了两个没有实现的方法
	Start() 
	Stop()
}


//声明/定义一个接口
type Usb2 interface {
	//声明了两个没有实现的方法
	Start() 
	Stop()
	Test()
}



type Phone struct {

}  

//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

type Camera struct {

}
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作~~~。。。")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作。。。")
}


//计算机
type Computer struct {

}

//编写一个方法Working 方法,接收一个Usb接口类型变量
//只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法)
func (c Computer) Working(usb Usb) {

	//通过usb接口变量来调用Start和Stop方法
	usb.Start()
	usb.Stop()
}

func main() {

	//测试
	//先创建结构体变量
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}

	//关键点
	computer.Working(phone)
	computer.Working(camera) //
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73

说明: 上面的代码就是一个接口编程的快速入门案例。

# 11.5.4 接口概念的再说明

interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个 自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

# 11.5.5 基本语法

【点击观看视频】Go接口特点和语法说明

image-20210115225240261

小结说明:

1 ) 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。

2 ) Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有 implement这样的关键字

# 11.5.6 接口使用的应用场景

【点击观看视频】Go接口应用场景的说明

image-20210115225348140

# 11.5.7 注意事项和细节

【点击观看视频】Go接口注意事项和细节(1)
【点击观看视频】Go接口注意事项和细节(2)

1 ) 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)

2 ) 接口中所有的方法都没有方法体,即都是没有实现的方法。

package main
import (
	"fmt"
)

type Ainterface interface{
    Say()
}

type Stu struct {
	Name string
}

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


func main() {
	var stu Stu //结构体变量,实现了 Say() 实现了 AInterface
 	var a AInterface = stu
	a.Say()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

3 ) 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现 了该接口。

4 ) 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

5 ) 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

package main
import (
	"fmt"
)

type Ainterface interface{
    Say()
}

type integer int

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

func main() {
	var i integer = 10
	var b AInterface = i
	b.Say() // integer Say i = 10
}	
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

6 ) 一个自定义类型可以实现多个接口

package main
import (
	"fmt"
)

type AInterface interface {
	Say()
}

type BInterface interface {
	Hello()
}
type Monster struct {

}
func (m Monster) Hello() {
	fmt.Println("Monster Hello()~~")
}

func (m Monster) Say() {
	fmt.Println("Monster Say()~~")
}

func main() {
	//Monster实现了AInterface 和 BInterface
	var monster Monster
	var a2 AInterface = monster
	var b2 BInterface = monster
	a2.Say()
	b2.Hello()
}
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

7 ) Golang接口中不能有任何变量

type AInterface interface {
	Name string //错误
	Say()
}
1
2
3
4

8 ) 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。

package main
import (
	"fmt"
)

type BInterface interface {
	test01()
}

type CInterface interface {
	test02()
}

type AInterface interface {
	BInterface
	CInterface
	test03()
}

//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
type Stu struct {
}
func (stu Stu) test01() {

}
func (stu Stu) test02() {
	
}
func (stu Stu) test03() {
	
}

type T  interface{

}

func main() {
	var stu Stu
	var a AInterface = stu
	a.test01()
}
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

9 ) interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil

10 ) 空接口interface{}没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量 赋给空接口。

package main
import (
	"fmt"
)

type BInterface interface {
	test01()
}

type CInterface interface {
	test02()
}

type AInterface interface {
	BInterface
	CInterface
	test03()
}

//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
type Stu struct {
}
func (stu Stu) test01() {

}
func (stu Stu) test02() {
	
}
func (stu Stu) test03() {
	
}

type T  interface{

}

func main() {
    var stu Stu
	var a AInterface = stu
	a.test01()
    
	var t T = stu //ok
	fmt.Println(t)
	var t2 interface{}  = 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
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

# 11.5.8 课堂练习

【点击观看视频】Go接口课堂练习题
//下面代码,有没有错误,你能得出什么结论?
package main
import (
	"fmt"
)

type AInterface interface {
	Test01()
	Test02()
}

type BInterface interface {
	Test01()
	Test03()
}

type Stu struct {
	AInterface
	BInterface
}

func (stu Stu) Test01(){

}

func (stu Stu) Test02(){

}

func (stu Stu) Test03(){

}
func main() {
	stu := Stu{}
	var a AInterface = stu
	var b BInterface = stu
	fmt.Println("ok~",a,b)
}
//OK
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
//下面代码,有没有错误,你能得出什么结论?
package main
import (
	"fmt"
)

type AInterface interface {
	Test01()
	Test02()
}

type BInterface interface {
	Test01()
	Test02()
}

type CInterface interface {
	AInterface
	BInterface
}

func main() {
	
}
//这里编译错误,因为Cinterface有两个Test01(),编译器不能通过!报告重复定义
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

# 11.5.9 接口编程的最佳实践

【点击观看视频】接口编程的经典案例
  • 实现对Hero结构体切片的排序:sort.Sort(dataInterface)
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)
}

//Less方法就是决定你使用什么标准进行排序
//1. 按Hero的年龄从小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
	return hs[i].Age < hs[j].Age
	//修改成对Name排序
	//return hs[i].Name < hs[j].Name
}

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]
}


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

//将Student的切片,安Score从大到小排序!!

func main() {

	//先定义一个数组/切片
	var intSlice = []int{0, -1, 10, 7, 90}
	//要求对 intSlice切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法 
	sort.Ints(intSlice) 
	fmt.Println(intSlice)

	//请大家对结构体切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法

	//测试看看我们是否可以对结构体切片进行排序
	var heroes HeroSlice
	for i := 0; i < 10 ; i++ {
		hero := Hero{
			Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),
			Age : rand.Intn(100),
		}
		//将 hero append到 heroes切片
		heroes = append(heroes, hero)
	}

	//看看排序前的顺序
	for _ , v := range heroes {
		fmt.Println(v)
	}

	//调用sort.Sort
	sort.Sort(heroes)
	fmt.Println("-----------排序后------------")
	//看看排序后的顺序
	for _ , v := range heroes {
		fmt.Println(v)
	}

	i := 10
	j := 20
	i, j = j, i
	fmt.Println("i=", i, "j=", j) // i=20 j = 10
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  • 接口编程的课后练习
// 1 .声明Student结构体
type Student struct{
    Name string
    Age int
    Score float64
}
//将Student的切片,安Score从大到小排序!!
1
2
3
4
5
6
7

# 11.5.10 实现接口vs继承

【点击观看视频】实现接口和继承比较(1)
【点击观看视频】实现接口和继承比较(2)
【点击观看视频】实现接口和继承比较(3)

大家听到现在,可能会对实现接口和继承比较迷茫了, 这个问题,那么他们究竟有什么区别呢

image-20210115231543470

代码说明:

package main
import (
	"fmt"
)

//Monkey结构体
type Monkey struct {
	Name string
}

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

type FishAble interface {
	Swimming()
}

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

//LittleMonkey结构体
type LittleMonkey struct {
	Monkey //继承
}


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

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

func main() {

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

}
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

对上面代码的小结

1 ) 当A结构体继承了B结构体,那么A结构就自动的继承了B结构体的字段和方法,并且可以直接使用

2 ) 当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充.

  • 实现接口可以看作是对 继承的一种补充

image-20210115231702844

  • 接口和继承解决的解决的问题不同

    继承的价值主要在于:解决代码的复用性和可维护性。

    接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

  • 接口比继承更加灵活 Person Student BirdAbleLittleMonkey

    接口比继承更加灵活,继承是满足 is-a的关系,而接口只需满足 like-a的关系。

  • 接口在一定程度上实现代码解耦

# 11.6 面向对象编程-多态

【点击观看视频】多态及体现

# 11.6.1 基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可 以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

# 11.6.2 快速入门

在前面的Usb接口案例,Usbusb ,既可以接收手机变量,又可以接收相机变量,就体现了Usb 接 口 多态特性。[点明]

image-20210115231808697

# 11.6.3 接口体现多态的两种形式

  • 多态参数

    在前面的Usb接口案例,Usbusb ,即可以接收手机变量,又可以接收相机变量,就体现了Usb 接口多态。

  • 多态数组

    演示一个案例:给Usb数组中,存放 Phone 结构体 和 Camera结构体变量

    案例说明:

package main
import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
	//声明了两个没有实现的方法
	Start()
	Stop()
}

type Phone struct {
	name string
}  

//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

func (p Phone) Call() {
	fmt.Println("手机 在打电话..")
}

type Camera struct {
	name string
}
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作。。。")
}


func main() {
	//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
	//这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}

	fmt.Println(usbArr)
}
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

# 11.7 类型断言

【点击观看视频】类型断言引出和基本使用

# 11.7.1 由一个具体的需要,引出了类型断言

package main
import (
	"fmt"
)
type Point struct {
	x int
	y int
}
func main() {
	var a interface{}
	var point Point = Point{1, 2}
	a = point  //oK
	// 如何将 a 赋给一个Point变量?
	var b Point
	// b = a 不可以
	// b = a.(Point) // 可以
	b = a.(Point) 
	fmt.Println(b) // 
}	
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20210115232125223

# 11.7.2 基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,

具体的如下:

package main
import (
	"fmt"
)
type Point struct {
	x int
	y int
}
func main() {
	//类型断言的其它案例
	var x interface{}
	var b2 float32 = 1.1
	x = b2  //空接口,可以接收任意类型
	// x=>float32 [使用类型断言]
	y := x.(float32)
	fmt.Printf("y 的类型是 %T 值是=%v", y, y)
}    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 对上面代码的说明:

在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型.

  • 如何在进行断言时,带上检测机制,如果成功就ok,否则也不要报panic
package main
import (
	"fmt"
)
type Point struct {
	x int
	y int
}
func main() {
	//类型断言(带检测的)
	var x interface{}
	var b2 float32 = 2.1
	x = b2  //空接口,可以接收任意类型
	// x=>float32 [使用类型断言]

	//类型断言(带检测的)
	if y, ok := x.(float32); ok {
		fmt.Println("convert success")
		fmt.Printf("y 的类型是 %T 值是=%v", y, y)
	} else {
		fmt.Println("convert fail")
	}
	fmt.Println("继续执行...")

}
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

# 11.7.3 类型断言的最佳实践 1

【点击观看视频】类型断言最佳实践(1)
【点击观看视频】类型断言最佳实践(2)
  • 在前面的Usb接口案例做改进:

    给Phone结构体增加一个特有的方法call(), 当Usb 接口接收的是Phone 变量时,还需要调用call方法, 走代码:

package main
import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
	//声明了两个没有实现的方法
	Start()
	Stop()
}

type Phone struct {
	name string
}  

//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

func (p Phone) Call() {
	fmt.Println("手机 在打电话..")
}


type Camera struct {
	name string
}
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作。。。")
}

type Computer struct {

}

func (computer Computer) Working(usb Usb) {
	usb.Start()
	//如果usb是指向Phone结构体变量,则还需要调用Call方法
	//类型断言..[注意体会!!!]
	if phone, ok := usb.(Phone); ok {
		phone.Call()
	}
	usb.Stop()
}

func main() {
	//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
	//这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}

	//遍历usbArr
	//Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,
	//除了调用Usb 接口声明的方法外,还需要调用Phone 特有方法 call. =》类型断言
	var computer Computer
	for _, v := range usbArr{
		computer.Working(v)
		fmt.Println()
	}
	//fmt.Println(usbArr)
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72

# 11.7.4 类型断言的最佳实践 2

写一函数,循环判断传入参数的类型:

package main
import (
	"fmt"
)

//定义Student类型
type Student struct {

}

//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
	for index, x := range items {
		switch x.(type) {
			case bool :
				fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
			case float32 :
				fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
			case float64 :
				fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
			case int, int32, int64 :
				fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
			case string :
				fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
			default :
				fmt.Printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
		}
	}
}

func main() {

	var n1 float32 = 1.1
	var n2 float64 = 2.3
	var n3 int32 = 30
	var name string = "tom"
	address := "北京"
	n4 := 300

	TypeJudge(n1, n2, n3, name, address, n4)
}
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

# 11.7.5 类型断言的最佳实践 3 【学员自己完成】

在前面代码的基础上,增加判断Student类型和 *Student类型

package main
import (
	"fmt"
)

//定义Student类型
type Student struct {

}

//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
	for index, x := range items {
		switch x.(type) {
			case bool :
				fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
			case float32 :
				fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
			case float64 :
				fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
			case int, int32, int64 :
				fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
			case string :
				fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
			case Student :
				fmt.Printf("第%v个参数是 Student 类型,值是%v\n", index, x)
			case *Student :
				fmt.Printf("第%v个参数是 *Student 类型,值是%v\n", index, x)
			default :
				fmt.Printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
		}
	}
}

func main() {

	var n1 float32 = 1.1
	var n2 float64 = 2.3
	var n3 int32 = 30
	var name string = "tom"
	address := "北京"
	n4 := 300

	stu1 := Student{}
	stu2 := &Student{}

	TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)

}
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
  • 11.1 VSCODE的使用
  • 11.1.1 VSCode使用技巧和经验
  • 11.2 面向对象编程思想-抽象
  • 11.2.1 抽象的介绍
  • 11.2.2 代码实现
  • 11.3 面向对象编程三大特性-封装
  • 11.3.1 基本介绍
  • 11.3.2 封装介绍
  • 11.3.3 封装的理解和好处
  • 11.3.4 如何体现封装
  • 11.3.5 封装的实现步骤
  • 11.3.6 快速入门案例
  • 11.3.7 课堂练习(学员先做)
  • 11.4 面向对象编程三大特性-继承
  • 11.4.1 看一个问题,引出继承的必要性
  • 11.4.2 继承基本介绍和示意图
  • 11.4.3 嵌套匿名结构体的基本语法
  • 11.4.4 快速入门案例
  • 11.4.5 继承给编程带来的便利
  • 11.4.6 继承的深入讨论
  • 11.4.7 课堂练习
  • 11.4.8 面向对象编程-多重继承
  • 11.5 接口(INTERFACE)
  • 11.5.1 基本介绍
  • 11.5.2 为什么有接口
  • 11.5.3 接口快速入门
  • 11.5.4 接口概念的再说明
  • 11.5.5 基本语法
  • 11.5.6 接口使用的应用场景
  • 11.5.7 注意事项和细节
  • 11.5.8 课堂练习
  • 11.5.9 接口编程的最佳实践
  • 11.5.10 实现接口vs继承
  • 11.6 面向对象编程-多态
  • 11.7 类型断言
  • 11.7.1 由一个具体的需要,引出了类型断言
  • 11.7.2 基本介绍
  • 11.7.3 类型断言的最佳实践 1
  • 11.7.4 类型断言的最佳实践 2
  • 11.7.5 类型断言的最佳实践 3 【学员自己完成】