开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:接口编程的经典案例】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9703
接口编程的经典案例
内容简介
一、 接口的最佳实践
二、 结构体切片排序的案例
一、 接口的最佳实践
在之前讲到过接口的基本语法、注意事项,同时还学习了基本的课堂练习,但没有具体的讲述接口的应用,就是到目前无法对接口有一个深刻的认识。在这通过讲述一个接口的实践来进一步学习。
接口最佳实践:实现对Hero结构体切片的排序:sort. Sort(data Interface)
接下来会写一个Hero结构体来定义一个结构体切片类型,并对结构体进行一个排序。
新建一个文件夹,命名为exercise02,之后再文件夹中新建一个文件命名为main.go
package main
import(
"fmt""sort"
)
func main () {
}
代码讲解:先进行一个打包package,再导入包import,写入函数func。
到目前,为什么说要对Hero结构体切片排序?在这做一个引导,在之前学到的对数组的排序用冒泡排序中讲过。先定义一个数组,在这以切片为例,回顾冒泡排序的用法(这样可以对接口最佳实践有更深刻的意识)
代码内容:
package main
import(
"fmt"
"sort"
)
func main () {
var intslice = []int{0, -1, 10,7,90}
sort.Ints(intSlice)
fmt.Println(intSlice)
}
代码讲解:
定义一个数组/切片,var intslice = []int{0, -1, 10,7,90},再目前的切片中是无序的。要求对 intslice切片进行排序?1.冒泡排序2.也可以使用系统提供的方法。要用第二的方法就表示要在import中在引入系统的sort包,在函数中写入sort把切片切入,在sort(intSlice)中是不需要加入地址的。原因是在前面讲述过一个最基础的一点,在变量中,切片是一种引用类型;当传进去就等于把main中的intSlice 传给sort()方法,也就是对sort()的内部对intSlice排序过后就会影响到intSlice切片。最后在输入这个intSlice,此时输出的就是一个有序的。注意:文档中关于sort的导包正确方法,这里存在的错误是没有在sort(intSlice)语句中将其写为sort.Ints(intSlice)
运行结果如下:
先cd到刚刚的chapter11,在cd进入exercise02,在输入go run main.go。下图可以看到运行的结果就是一个正确的排序
查找使用系统提供的方法,系统提供的方法在手册中。手册在官方网站中(网址为https://golang .org)的Packages中有一个叫做sort包,点进包后有一个function为Ints这个就是对int的切片进行一个排序,默认的是升序。下图中的黄色部分为例子。
如果感兴趣可以在刚刚ints的页面中点击进入源代码,
源代码为:func Ints(a []int) { Sort (IntSlice(a)) }
之后要求对结构体进行排序:1.冒泡排序2.使用系统提供的方法
查找方法:在官方网站中(网址为https://golang .org)的Packages中有一个叫做sort包,点进包后有一个方法就叫Sort(data Interface),它接收的是一个接口,把接口传进去,就是Sort可以接收一个由接口接入的变量。这个接口里面有三个方法需要实现分别是len()、less()、swap(),就是如果需要去调用系统中的方法去给结构体的切片排序就要实现这接口的三个方法。在Sort里面就会用到这三个接口的方法。
点击Sort里面就会看到一个len方法,如下:
func Sort(data Interface) {
n:=data.Len()
quicksort(data,0,n,maxDepth(n))
}
也就是在上面代码中Sort方法对data排序的时候会用到len,同时默认使用quickSort快速排序法,就是把data继续传到quickSort中。而在这里面会继续用到Less和Swap这俩个方法,点击sort会追踪到quickSort,在右上的搜索框中搜索quickSort。下图是quickSort的源代码,可以看到已经拿到data,就会执行一个快速排序法的一个操作,可以在这里面找到刚才实现的另外两个方法,Less和Swap。如果一个切片中可以实现这三个方法,那么就可以调用Sort来完成一个对结构体切片的排序。总之不管是什么类型的,只要满足这个三个方法,就可以实现结构体切片的排序。
二、结构体切片排序的案例
已经在前面的讲解把接口说的比较清楚,对接口已经有比较深刻的认识,接下来进行对结构体切片排序的案例,来体会接口的编程,这也是面向对象编程的核心。
代码内容如下:
package main
import(
"fmt"
"sort"
)
type Hero struct{
Name string
Age int
}
type HeroSlice []Hero
func (hs HeroSlice) Len() int {
return len (HeroSlice)
}
func (hs Herolice) Less(i,j int) bool {
return hs [i].Age > hs [i].Age
}
func (hs Heroslice) Swap(i,j int) {
temp :=hs[i]
hs[i] =hs[j]
hs[j]=temp
}
func main () {
var intslice = []int{0, -1, 10,7,90}
sort.Ints(intSlice)
fmt.Println(intSlice)
}
for i : = 0; i < 10 ; i++ {
hero : =Hero{
Name :fmt.sprintf("英雄~%d",rand.Intn(100)),
Age:rand.Intn(100),
}
heros=append (heroes,hero)heroes
}
for _ ,v:=range heros {
fmt.Println(v)
}
}
代码解释:
1. 声明Hero结构体,在里面加入两个字段(可以自定),一个是Name、一个是年龄。
type Hero struct{
Name string
Age int
}
2. 声明一个Hero结构体切片类型,使用切片类型的原因是可以放置多个Hero的变量
type HeroSlice []Hero
3. 把Sort中对应的方法实现,传一个切片变量进去让他实现。实现Intrface 接口。接口的第一个方法是len,它可以返回一个集合的长度或者是元素的个数。
func (hs HeroSlice)
Len() int {
return len (HeroSlice)
}
func (hs Herolice) Less(i,j int) bool {
return len (Heroslice)
}
第二个方法是Less(i,j int),它是会返回一个布尔值,如果下标为i的变量大于下标j的变量,就会按照升序排;若下标为i的值小于下表为j的值为真,那么这个排列就是降序排列
Less方法就是决定你使用什么标准进行排序
按Hero的年龄从小到大排序,只要这个返回值为真,就是a按年龄升序排序。这是一个重点
func (hs Heroslice) Less(i,j int) bool {
return hs [i].Age > hs [i].Age
}
第三个方法是Swap(i,j int),就是把i和j的元素交换,这个方法在快速排序中会被调用(是通过接口调用的,前面已经看过源代码),交换的时候没有返回值。是先把i对应的这个元素交给一个变量,再将j对应的元素赋给i对应的元素,最后再将这个变量的值赋给j对应的值。
func (hs Heroslice) Swap(i,j int) {
temp :=hs[i]
hs[i] =hs[j]
hs[j]=temp
}
这三个方法都实现了就意味着可以完成interface接口,就可以调用Sort中的Sort方法。
4.测试,看接口是否可以对结构体切片进行排序。所以需要做一些切片出来去测试。定义一个切片:var heroes Heroslice
一个一个放入切片比较麻烦,会通过循环进行放入,先创建一个hero的结构变量,fmt.sprintf会返回一个格式化的字符串,还涉及到一个rand方法(rand.Intn(100)会返回一个0~100的随机数);写名字的中还需要查找一个math的包,下图有关于rand的写法,有一个Intn方法,返回一个0-n的随机值;年龄也是随机数。
还要再import下面导入一个”math/rand” ,for循环结束后要将 hero append到heroes切片中,注意append后面还要接受一下,因为并没有放进去
for i : = 0; i < 10 ; i++ {
hero : =Hero{
Name :fmt.sprintf("英雄~%d",rand.Intn(100)),
Age:rand.Intn(100)
}
heroes =append (heroes,hero)
}
5.看看排序前的顺序,用一个for()循环可以搞定
for _ ,v:=range heros {
fmt.Println(v)
}
6.运行检查发现错误,在写接口中的返回值应该为return len(hs)
运行结果如下:
输入 go run main.go,结果如下:
认真观察会发现年龄不是有序的。
7.之后再调用sort.Sort,把切片放进去变为sort.Sort(heros)。注意之所以可以把切片的类型放进去是因为heros对应的切片类型实现了Sort下面的三个方法。此时也没有报错,而如果把其中的一个方法注销掉,再编译就会出现报错,这在前面讲到过,下图为错误提示。错误提示内容表示HeroSlice没有实现sort下面的接口,特别的是这个接口就叫Interface,显示没有实现之前注销掉的方法是Swap
之后加入排序后的顺序
for _ ,v:=range heros {
fmt.Println(v)
}
为了方便清楚的观察,在sort调用后加入一句fmt.Print(“-------------排序后-------------”)
代码内容如下:
package main
import(
"fmt"
"sort"
“math”
)
type Hero struct{
Name string
Age int
}
type HeroSlice []Hero
func (hs HeroSlice) Len() int {
return len(hs)
}
func (hs Herolice) Less(i,j int) bool {
return hs [i].Age > hs [i].Age
}
func (hs Heroslice) Swap(i,j int) {
temp :=hs[i]
hs[i] =hs[j]
hs[j]=temp
}
func main () {
var intslice = []int{0, -1, 10,7,90}
sort.Ints(intSlice)
fmt.Println(intSlice)
}
for i : = 0; i < 10 ; i++ {
hero : =Hero{
Name :fmt.sprintf("英雄~%d",rand.Intn(100)),
Age:rand.Intn(100),
}
heros=append (heroes,hero)heroes
}
for _ ,v:=range heros {
fmt.Println(v)
sort.Sort(heros)
fmt.Print(“-------------排序后-------------”)
for
_ ,v:=range heros {
fmt.Println(v)
}
}
}
运行结果为:
可以看到结果是降序排列的,是因为之前写的Less中返回值是>,需要修改为>,才能是升序的
此时修改<,可改为升序排列,在quickswap中可以知道
也可以改成对姓名排序
只要将return对象改为名字即可
如果感兴趣的可以查找源代码,在官网中找到,在sort包中的sort方法里面有一个quickSort,在右上角搜索quickSort可以找到鼠标所在位置的代码,如果说if data.Less(i,i-6)为真,那么就会执行data.Swap(I,i-6)的语句。
原来对年龄进行一个排序,现在相对姓名排序,如何修改?
现已经明白func (hs Herolice) Less(i,j int) bool {
return hs [i].Age > hs [i].Age
这就是一个标准,改成名字排序,直接修改return语句即可
func (hs Herolice) Less(i,j int) bool {
return hs [i].Name > hs [i].Name
现在就是按照名字排序,若i小于j为真,就会升序排序
运行结果:会发现是按照名字的升序排列的,如果要改成降序排列的直接修改return hs [i].Name > hs [i].Name的>符号为<即可。
还可以在添加别的结构体叫学生
type Student struct{
Name string
Age int
Score float64
}
要求定义许多学生就是要用到切片,是多个学生就要用到接口,只要实现sort下的三个方法,剩下的是交给sort去完成,这种效率是很高的,也就是实现了一个接口,把这个接口实现就会按照排序的顺序自动完成,这就是接口的好处。在这里是Student结构体,除此之外还可以有别的结构体、别的变量等都是可以存在的 。可以接口一个名字、年龄一起排序。
注意:
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]。就是
temp :=hs[i]
hs[i] =hs[j]
hs[j]=temp
这三行可以直接用刚刚的一句代码完成同样的效果,为了看到变化在Name中的英雄~改为了英雄|,运行结果如下图;
可以看到还是按照年龄的顺序排放
现在假设面试官出了一个问题,下列语句是否可以完成交换?
i:=10
j:=20
i,j = j,i
fmt.Println(“i=”,i”j=”j)
这个是可以完成交换,变为i=20,j=10
运行结果如下:
以上就是接口的实践,可以通过这个案例自行设计一些接口。在这个Sort中的使用需要用到三个方法才能够实现,这个Sort的核心代码是在quickSort中,它在quickSort中又调用了一系列的排序规则,这种方式有助于写高质量的代码,开放性写代码。同时把核心代码由程序员编写,把可以放开的部分交给程序实现。在这只需要告诉Len、Less、Swap,在这里的Less是告诉排序的标准是什么,如果把Swap放开才能让别人知道类型是什么(i、j只是一个下标,如果只知道下标不清楚类型),能放开就放开,不能就写到自己的类上去。
课后要求:将Student的切片,按照Score从大到小排序