Go第 11 章 :面向对象编程(下)
创始人
2024-05-08 09:38:18
0

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

11.1 VSCode 的使用

11.1.1 VSCode 使用技巧和经验

请添加图片描述
请添加图片描述
请添加图片描述

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

11.2.1 抽象的介绍

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

11.2.2 代码实现

package mainimport "fmt"type Account struct {Name    stringPwd     stringBalance float64
}func (a *Account) Despoit(money float64, pwd string) {lable1:if a.Pwd != pwd {fmt.Println("password error!")fmt.Println("请重新输入正确的密码!")fmt.Scanln(&pwd)goto lable1}lable2:if money < 0 {fmt.Println("input money is error!")fmt.Println("请重新输入正确的金额!")fmt.Scanln(&money)goto lable2}a.Balance += money
}func (a *Account) WithDraw(money float64, pwd string) {lable1:if a.Pwd != pwd {fmt.Println("password error!")fmt.Println("请重新输入正确的密码!")fmt.Scanln(&pwd)goto lable1}lable2:if money < 0 || money > a.Balance {fmt.Println("input money is error!")fmt.Println("请重新输入正确的金额!")fmt.Scanln(&money)goto lable2}a.Balance -= money
}
func (a *Account)Query(name string,pwd string){lable1:if a.Name != name{fmt.Println("users name is error!")fmt.Println("请重新输入正确的用户名!")fmt.Scanln(&name)goto lable1}lable2:if a.Pwd != pwd {fmt.Println("password error!")fmt.Println("请重新输入正确的密码!")fmt.Scanln(&pwd)goto lable2}fmt.Println("账户:",a.Name,"的余额是:",a.Balance)
}
func main () {a := Account{Name:"田毅",Pwd:"888888",Balance: 100.0,}b := Account{Name:"杨璐羽",Pwd:"666666",Balance:100.0,}a.Query("田毅","888888")a.WithDraw(50,"888888")a.Despoit(1000000,"888888")a.Query("田毅","888888")b.Query("杨璐羽","666666")b.WithDraw(50,"666666")b.Despoit(100000,"666666")b.Query("杨璐","6666666")}

请添加图片描述
对上面代码的要求

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

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

11.3.1 基本介绍

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

11.3.2 封装介绍

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

11.3.3 封装的理解和好处

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

11.3.4 如何体现封装

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

11.3.5 封装的实现步骤

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

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

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

  4. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
    func (var 结构体类型名) GetXxx() {
    return var.age;
    }

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

11.3.6 快速入门案例

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

 代码实现
model/person.go

package modelimport "fmt"type person struct {Name stringage  int //其它包不能直接使用sal  float64
}func NewPerson(name string) *person {return &person{Name: name,}
}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
}

main/main.go

package mainimport ("fmt"model "go_code/go_code/chapter11/model"
)func main() {p:=model.NewPerson("tianyi")p.SetAge(18)p.SetSal(5000)fmt.Println(p)fmt.Println(p.Name,"ag=",p.GetAge(),"sal=",p.GetSal())
}

11.3.7 课堂练习(学员先做)

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

model

package accountsimport "fmt"type Account struct{username stringbalance intpwd string
}func (a *Account) SetUsername (n string){if !(len(n)>=6&&len(n)<=10){fmt.Println("用户名格式错误!请输入6-10位的用户名:")fmt.Scanln(&n)}a.username=n
}func (a *Account) GetUsername() string {return a.username
}func (a *Account) SetBalance (b int){if b<20{fmt.Println(a.username,"的余额小于20!请存入大于20的金额:")fmt.Scanln(&b)}a.balance=b
}func (a *Account) GetBalance() int {return a.balance
}func (a *Account) SetPwd (p string){if len(p)!=6{fmt.Println(a.username,"的密码格式错误!请输入6位用户密码:")fmt.Scanln(&p)}a.pwd=p
}func (a *Account) GetPwd() string {return a.pwd
}func (a *Account) Deposite(money int, pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}if money <= 0 {fmt.Println("您输入的金额数不正确!请重新输入:")fmt.Scanln(&money)}a.balance += moneyfmt.Println("存款成功")
}
func (a *Account) WithDraw(money int, pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}if money <= 0 || money > a.balance {fmt.Println("您输入的金额数不正确!请重新输入:")fmt.Scanln(&money)}a.balance -= moneyfmt.Println("取款成功")
}func (a *Account) Query(pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}fmt.Printf("你的账号为=%v 余额=%v \n", a.username, a.balance)
}

main

package mainimport ("fmt"accounts "go_code/go_code/chapter11/account"model "go_code/go_code/chapter11/model"
)func main01() {p:=model.NewPerson("tianyi")p.SetAge(18)p.SetSal(5000)fmt.Println(p)fmt.Println(p.Name,"ag=",p.GetAge(),"sal=",p.GetSal())
}
func main(){var account1 accounts.Accountaccount1.SetUsername("田毅")account1.SetBalance(10000)account1.SetPwd("888888")var account2 accounts.Accountaccount2.SetUsername("小羊")account2.SetBalance(10)account2.SetPwd("8888888")fmt.Println(account1)fmt.Println(account2)fmt.Printf("用户:%v 余额:%v 密码:%v\n",account1.GetUsername(),account1.GetBalance(),account1.GetPwd())fmt.Printf("用户:%v 余额:%v 密码:%v\n",account2.GetUsername(),account2.GetBalance(),account2.GetPwd())}
工厂模式函数的方法

model

package factoryimport "fmt"type account struct {accountNo stringpwd       stringbalance   float64
}func NweAccount(accountNo string, pwd string, balance float64) *account {if !(len(accountNo) >= 6 && len(accountNo) <= 10) {fmt.Println("用户名格式错误!请输入6-10位的用户名:")fmt.Scanln(&accountNo)}if balance < 20 {fmt.Println(accountNo, "的余额小于20!请存入大于20的金额:")fmt.Scanln(&balance)}if len(pwd) != 6 {fmt.Println(accountNo, "的密码格式错误!请输入6位用户密码:")fmt.Scanln(&pwd)}return &account{accountNo: accountNo,pwd:       pwd,balance:   balance,}
}
func (a *account) Deposite(money float64, pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}if money <= 0 {fmt.Println("您输入的金额数不正确!请重新输入:")fmt.Scanln(&money)}a.balance += moneyfmt.Println("存款成功")
}
func (a *account) WithDraw(money float64, pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}if money <= 0 || money > a.balance {fmt.Println("您输入的金额数不正确!请重新输入:")fmt.Scanln(&money)}a.balance -= moneyfmt.Println("取款成功")
}func (a *account) Query(pwd string) {for i := 0; i < 3; i++ {if a.pwd != pwd {fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")fmt.Scanln(&pwd)}break}fmt.Printf("你的账号为=%v 余额=%v \n", a.accountNo, a.balance)
}func (a *account) GetBalance() float64 {return a.balance
}func (a *account) GetPwd() string {return a.pwd
}func (a *account) GetUsername() string {return a.accountNo
}

main

func main() {account1:=factory.NweAccount("tianyi","000",10)account2:=factory.NweAccount("yangluyu","666666",10000)fmt.Printf("用户:%v 余额:%v 密码:%v\n", account1.GetUsername(), account1.GetBalance(), account1.GetPwd())fmt.Printf("用户:%v 余额:%v 密码:%v\n", account2.GetUsername(), account2.GetBalance(), account2.GetPwd())
}

 说明:在老师的代码基础上增加如下功能: 通过 SetXxx 的方法给 Account 的字段赋值 通过 GetXxx方法获取字段的值。(同学们自己完成) 在 main函数中测试

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

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

一个小问题,看个学生考试系统的程序 extends01.go,提出代码复用的问题
请添加图片描述
走一下代码

package mainimport ("fmt"
)//编写一个学生考试系统
//小学生
type Pupil struct {Name  stringAge   intScore int
}//显示他的成绩
func (p *Pupil) ShowInfo() {fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}
func (p *Pupil) SetScore(score int) {//业务判断p.Score = score
}
func (p *Pupil) testing() {fmt.Println("小学生正在考试中.....")
}//大学生, 研究生。。
//大学生
type Graduate struct {Name  stringAge   intScore int
}//显示他的成绩
func (p *Graduate) ShowInfo() {fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}
func (p *Graduate) SetScore(score int) {//业务判断p.Score = score
}
func (p *Graduate) testing() {fmt.Println("大学生正在考试中.....")
}//代码冗余.. 高中生....
func main() {//测试var pupil = &Pupil{Name: "tom",Age:  10,}pupil.testing()pupil.SetScore(90)pupil.ShowInfo()//测试var graduate = &Graduate{Name: "mary",Age:  20}graduate.testing()graduate.SetScore(90)graduate.ShowInfo()
}
对上面代码的小结
  1. Pupil 和 Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
  3. 解决方法-通过继承方式来解决

11.4.2 继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的
Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student 匿名结构体即可。 [画 出示意图]

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

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

type Goods struct {
Name string Price int
}
type Book struct {
Goods //这里就是嵌套匿名结构体
Goods Writer string
}

11.4.4 快速入门案例

 案例
我们对 extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处

代码实现

package mainimport ("fmt"
)//编写一个学生考试系统
type Student struct {Name  stringAge   intScore 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
}//小学生
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 = 8pupil.testing()pupil.Student.SetScore(70)pupil.Student.ShowInfo()graduate := &Graduate{}graduate.Student.Name = "mary~"graduate.Student.Age = 28graduate.testing()graduate.Student.SetScore(90)graduate.Student.ShowInfo()
}

11.4.5 继承给编程带来的便利

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

11.4.6 继承的深入讨论

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,
    即:首字母大写或者小写的字段、方法, 都可以使用。【举例说明】
    请添加图片描述

  2. 匿名结构体字段访问可以简化,如图
    请添加图片描述

对上面的代码小结
  • (1) 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name
  • (2) 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段
  • (3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找…如果都找不到就报错.
  1. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问
    匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
    请添加图片描述
  2. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身 没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
    请添加图片描述
  3. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合 的结构体的字段或方法时,必须带上结构体的名字
    请添加图片描述
  4. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    请添加图片描述
    请添加图片描述

11.4.7 课堂练习

结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么
请添加图片描述
说明

  1. 如果一个结构体有 int 类型的匿名字段,就不能第二个。
  2. 如果需要有多个 int 的字段,则必须给 int 字段指定名字

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

多重继承说明
如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方 法,从而实现了多重继承。
请添加图片描述
多重继承细节说明

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来 区分。【案例演示】
    请添加图片描述
2) 为了保证代码的简洁性,建议大家尽量不使用多重继承

11.5 接口(interface)

11.5.1 基本介绍

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

11.5.2 为什么有接口

请添加图片描述

11.5.3 接口快速入门

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

代码实现

package mainimport ("fmt"
)//声明/定义一个接口
type Usb interface {//声明了两个没有实现的方法Start()Stop()
}
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 变量会根据传入的实参,来判断到底是 Phone,还是 Camera//通过 usb 接口变量来调用 Start 和 Stop 方法usb.Start()usb.Stop()
}
func main() {//测试 //先创建结构体变量computer := Computer{}phone := Phone{}camera := Camera{}//关键点computer.Working(phone)computer.Working(camera)
}

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

高内聚、低耦合 体现

11.5.4 接口概念的再说明

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

11.5.5 基本语法

请添加图片描述
请添加图片描述

11.5.6 接口使用的应用场景

请添加图片描述
请添加图片描述
请添加图片描述

请添加图片描述

11.5.7 注意事项和细节

  1. 接口本身不能创建实例,但是**可以指向一个实现了该接口的自定义类型的变量(**实例)
    请添加图片描述
    2)接口中所有的方法都没有方法体,即都是没有实现的方法。
    3)在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
    4)一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
    5)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
    请添加图片描述
  2. 一个自定义类型可以实现多个接口
    请添加图片描述
  3. Golang 接口中不能有任何变量
    请添加图片描述
  4. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。
    请添加图片描述
    请添加图片描述
  5. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10) 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。

请添加图片描述
请添加图片描述

11.5.8 课堂练习

请添加图片描述
请添加图片描述
请添加图片描述

11.5.9 接口编程的最佳实践

实现对 Hero 结构体切片的排序: sort.Sort(data Interface)

package mainimport ("fmt""math/rand""sort"
)//1.声明 Hero 结构体
type Hero struct {Name stringAge  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  stringAge   intScore float64
}//2,声明 StudentSlice切片
type StudentSlice []Student//3、使用切片实现Interface接口的方法
func (st StudentSlice) Len() int {return len(st)
}
func (st StudentSlice) Less(i, j int) bool {return st[i].Score < st[j].Score && st[i].Agest[i], st[j] = st[j], st[i]
}//将 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 HeroSlicefor 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.Sortsort.Sort(heroes)fmt.Println("-----------排序后------------")//看看排序后的顺序for _, v := range heroes {fmt.Println(v)}i := 10j := 20i, j = j, ifmt.Println("i=", i, "j=", j) // i=20 j = 10var students StudentSlicefor i:=0;i<10;i++{student := Student{Name:  fmt.Sprintf("张|%d",rand.Intn(100)),Age:   rand.Intn(100),Score: float64(rand.Intn(100)),}students=append(students,student)}fmt.Println("-----------排序前------------")for _,v :=range students{fmt.Println(v)}sort.Sort(students)fmt.Println("-----------排序后------------")for _,v :=range students{fmt.Println(v)}}

接口编程的课后练习
//1.声明 Student 结构体
type Student struct{
Name string Age int Score float64
}
//将 Student 的切片,安 Score 从大到小排序!!

11.5.10 实现接口 vs 继承

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

对上面代码的小结
  1. 当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直 接使用
  2. 当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我 们可以认为:实现接口是对继承机制的补充.
实现接口可以看作是对 继承的一种补充

请添加图片描述

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

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

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

接口比继承更加灵活 Person Student BirdAble LittleMonkey

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

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

11.6 面向对象编程-多态

11.6.1 基本介绍

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

11.6.2 快速入门

在前面的 Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 Usb 接 口 多态特性。[点明]
请添加图片描述

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

请添加图片描述
请添加图片描述
案例说明:

package mainimport ("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("手机停止工作。。。")
}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]UsbusbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"尼康"}fmt.Println(usbArr)usbArr[0].Start()usbArr[0].Stop()
}

11.7 类型断言

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

请添加图片描述

11.7.2 基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,
请添加图片描述

对上面代码的说明:

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

如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

请添加图片描述

11.7.3 类型断言的最佳实践 1

在前面的 Usb 接口案例做改进:

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

package mainimport ("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 (c 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]UsbusbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"尼康"}//fmt.Println(usbArr)//usbArr[0].Start()//usbArr[0].Stop()//遍历 usbArr//Phone 还有一个特有的方法 call(),请遍历 Usb 数组,如果是 Phone 变量,//除了调用 Usb 接口声明的方法外,还需要调用 Phone 特有方法 call. =》类型断言var computer Computerfor _,v:= range usbArr{computer.Working(v)fmt.Println()}fmt.Println(usbArr)
}

11.7.4 类型断言的最佳实践 2

写一函数,循环判断传入参数的类型:
请添加图片描述
请添加图片描述

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

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

package mainimport ("fmt"
)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个参数是bool类型,值是%v\n", index, x)case float64:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case int, int32, int64:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case string:fmt.Printf("第%v个参数是bool类型,值是%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("第%vv个参数是 类型不确定,值是%v\n", index, x)}}
}func main() {var n1 float32 = 1.1var n2 float64 = 2.3var n3 int32 = 30var name string = "tom"address := "北京"n4 := 300n5 :=Student{}n6:=&Student{}TypeJudge(n1, n2, n3, name, address, n4,n5,n6)
}

相关内容

热门资讯

中国男篮世预赛集训名单公布:周... 2025年11月12日,中国男篮正式公布了备战国际篮联2027篮球世界杯预选赛的集训名单。在这份名单...
国台办:越早实现祖国统一,越有...   “两岸关系的谈判协商:从回顾中找解方”座谈会日前在台北举行,与会人士呼吁在坚持“九二共识”基础上...
二十届四中全会名词卡片:区域创...   延伸阅读  怎样完善区域创新体系:  完善区域创新体系,关键是要统筹布局建设区域科技创新中心和产...
共享发展成果 共创美好未来 乌...   11月9日,2025年世界互联网大会乌镇峰会落下帷幕。古镇千年,是历史长河里的一瞬;峰会12年,...
“十四五”以来那曲市推进高质量...   “十四五”的春风掠过羌塘草原,在那曲这片壮美的草原上催生出万千气象。湛蓝如洗的天空下,成群的牦牛...
土耳其坠毁军用运输机上20名该...   中新社北京11月12日电 安卡拉消息:当地时间11月12日,土耳其国防部长亚沙尔·居莱尔证实,1...
涉退役军人违法违规账号被处置—...   ⇧点蓝色字关注“互联网联合辟谣平台”  2025年11月12日  谣 言江苏车牌将启动新字母号段...
涉退役军人违法违规账号处置典型...   根据“网上涉退役军人不当行为和有害信息内容专项整治”工作安排,近期,我办指导各网站平台从严整治以...
【2025打卡中国】震撼、宏伟...   视频策划及制作:杨心怡 陈超然  什么样的“钢铁巨无霸”能让外国博主们直呼震撼?11月11日,2...
(粤港澳全运会)十五运会的圣火...   中新网北京11月11日电 题:十五运会的圣火,在南粤大地画了一个圈  记者 曾玥  第十五届全国...