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

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

11.1 VSCode 的使用

11.1.1 VSCode 使用技巧和经验

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

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

11.2.1 抽象的介绍

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

11.2.2 代码实现
package main

import "fmt"

type Account struct {
	Name    string
	Pwd     string
	Balance 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 model

import "fmt"

type person struct {
	Name string
	age  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 main

import (
	"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 accounts

import "fmt"

type Account struct{
	username string
	balance int
	pwd 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 += money
	fmt.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 -= money
	fmt.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 main

import (
	"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.Account
	account1.SetUsername("田毅")
	account1.SetBalance(10000)
	account1.SetPwd("888888")

	var account2 accounts.Account
	account2.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 factory

import "fmt"

type account struct {
	accountNo string
	pwd       string
	balance   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 += money
	fmt.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 -= money
	fmt.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 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) SetScore(score int) {
	//业务判断
	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) 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 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
}

//小学生
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()
	graduate := &Graduate{}
	graduate.Student.Name = "mary~"
	graduate.Student.Age = 28
	graduate.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 main

import (
	"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 main

import (
	"fmt"
	"math/rand"
	"sort"
)

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

//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].Age<st[j].Age
}
func (st StudentSlice) Swap(i, j int) {
	st[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 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


	var students StudentSlice
	for 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 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("手机停止工作。。。")
}

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)
	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 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 (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]Usb
	usbArr[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 Computer
	for _,v:= range usbArr{
		computer.Working(v)
		fmt.Println()
	}
	fmt.Println(usbArr)
}

11.7.4 类型断言的最佳实践 2

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

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

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

package main

import (
	"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.1
	var n2 float64 = 2.3
	var n3 int32 = 30
	var name string = "tom"
	address := "北京"
	n4 := 300
	n5 :=Student{}
	n6:=&Student{}
	TypeJudge(n1, n2, n3, name, address, n4,n5,n6)
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值