1. 接口定义和使用
Golang在函数和方法的定义中要严格规定参数和返回值的数据类型,但是在部分函数中,比如 fmt.Println() 却可以传入任意类型的变量,要实现这样的功能就必须要通过接口(interface)。
1.1. 定义接口
接口是一种抽象的数据类型!接口(interface)是实现一组方法(method)的特殊数据类型,接口只关心方法,而不关心属性。
- 接口名:Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包之外的代码访问
- 参数:可以和结构体方法一致的格式
name string,也可以仅使用类型string - 返回值:与函数和方法一致
type interfaceName interface {methodName1(args)(res)methodName2(args)(res)...}
1.2. 接口的实现
当一个类型的方法满足了接口中定义的所有方法时,称之为这个类型实现了这个接口。如以下的代码中,redis和mysql类型就实现了backuper的接口!Golang中接口不需要显示的声明,即不需要显示的指明某个类型实现了某某接口! ```go type backuper interface { backup() }
type redis struct {name string; dstPath string} type mysql struct {name string}
func (r redis)backup() { fmt.Printf(“%s 备份完毕,备份存在路径: %s\n”, r.name,r.dstPath) }
func (m mysql)backup() { fmt.Println(m.name, “备份完毕!”) }
<a name="1092o"></a>### 1.3. 接口的使用方式<a name="mPM2L"></a>#### 1.3.1. 使用方式一1. 定义一个接口变量,该变量为 nil 。line:211. 使用满足接口的结构体初始化该变量。line:23 line:251. 使用该变量执行接口中的方法。line24 line26```gopackage mainimport "fmt"type backuper interface {backup()}type redis struct {name string; dstPath string}type mysql struct {name string}func (r redis)backup() {fmt.Printf("%s 备份完毕,备份存在路径: %s\n", r.name,r.dstPath)}func (m mysql)backup() {fmt.Println(m.name, "备份完毕!")}func main() {var db backuperfmt.Printf("%T %#v\n", db, db)db = redis{"Redis","/data/backup/redis"}db.backup()db = mysql{"MySQL"}db.backup()}
[root@heyingsheng day05]# go run 02-interface/main.go<nil> <nil>Redis 备份完毕,备份存在路径: /data/backup/redisMySQL 备份完毕!
1.3.2. 使用方法二
- 定义一个函数,接收的参数为接口类型。line:24
- 将实现接口的结构体实例作为参数传递给已经定义的函数。line:31 line:32 ```go package main
import “fmt”
type redis struct { name string dstPath string }
func (r redis) backup() { fmt.Printf(“%s 备份完毕,备份存在路径: %s\n”, r.name, r.dstPath) }
type mysql struct{ name string }
func (m mysql) backup() { fmt.Println(m.name, “备份完毕!”) }
type backuper interface { backup() }
func backup(b backuper) { b.backup() }
func main() { redis := redis{“Redis”, “/opt/backup/redis”} mysql := mysql{“MySQL”} backup(redis) backup(mysql) }
<a name="CKcEW"></a>### 1.4. 接口的注意事项- 接口是个引用类型,使用var 声明一个变量为某个接口后,变量的值是 nil ,只有该变量指向了实现该接口的自定义类型变量才能完成实例化!- 接口中不支持属性的定义,只能定义需要实现的方法,实现这些方法的任务在于各种数据类型- 实现接口的数据类型不仅仅可以是结构体,还可以是其它数据类型,但用的最多的是结构体类型---<a name="tqYFN"></a>## 2. 接口的功能接口的目的是为了屏蔽不同数据类型带来的影响,比如屏蔽redis和mysql两个结构体实例的差异,让一个接口变量能初始化为redis和mysql实例。另外在调用结构体方法中,解决方法对参数的强约问题,让redis和mysql都可以调用同一个函数。<a name="iRLYc"></a>### 2.1. 接口的方法类型自定义类型(包括结构体)的方法有两大类,分别是接收者为值和接收者为指针两种。在通常情况下都是建议使用指针类型接收者的方法。由指针接收者方法实现的接口 和 由值接收者方法实现的接口有一些区别。具体如下:<a name="Mx6tO"></a>#### 2.1.1. 值类型的方法用值接收者的方法实现的接口中,可以使用值去调用方法,也可以使用指针去调用。即下面案例中,app类型的变量 ss 既可以初始化为结构体指针,也可以初始化为结构体。之所以可以初始化为指针,是因为接收者为值类型的方法存在语法糖,会自动对指针取值。```gopackage mainimport ("fmt""strings")type service struct {name stringport uint16}// 基于值接收者的方法func (s service) manager(operate string) {switch operate {case "start":fmt.Printf("%v start! Listen port 0.0.0.0:%d\n", s.name, s.port)case "stop":fmt.Printf("%s stop!Release port %d\n", s.name, s.port)}}type app interface {manager(string) // manager() 是值类型接收者实现的方法}func main() {var ss appss = service{name: "sshd",port: 22,}ss.manager("start") // 结构体值 调用manager(),正常返回fmt.Println(strings.Repeat("-",30))ss = &service{name: "Apache",port: 80,}ss.manager("start") // 结构体指针 调用manager(),正常返回。语法糖fmt.Println(strings.Repeat("-",30))nginx := &service{name: "Nginx",port: 80,}nginx.manager("stop") // 结构体指针 调用自己的方法,不走接口,正常返回。语法糖}
[root@heyingsheng day05]# go run 04-value/main.gosshd start! Listen port 0.0.0.0:22------------------------------Apache start! Listen port 0.0.0.0:80------------------------------Nginx stop!Release port 80
2.1.2. 指针类型的方法(常用)
因为指针接收者的方法较为常用,因此使用指针接收者的方法实现的接口也比较常用。指针类型方法实现的接口,不能兼容值类型结构体对象!
package mainimport ("fmt")type service struct {name stringport uint16}//基于指针接收者的方法func (s *service)manager(operate string) {switch operate {case "start":fmt.Printf("%v start! Listen port 0.0.0.0:%d\n", s.name, s.port)case "stop":fmt.Printf("%s stop!Release port %d\n", s.name, s.port)}}type app interface {manager(string) // manager() 是值类型接收者实现的方法}func main() {var ss app//ss = service{// name: "sshd",// port: 22,//}//ss.manager("start") // 结构体值 调用manager(),异常//fmt.Println(strings.Repeat("-",30))ss = &service{name: "Apache",port: 80,}ss.manager("start") // 结构体指针 调用manager(),正常返回。}
2.2. 空接口
2.2.1. 空接口的用途
对于 fmt.Println() 函数能接收任意类型的值作为参数,原因是源码中接收的参数为 a ...inteface{} ,表示支持不定长传参,参数类型为 interface{} 。 interface{} 表示空接口,即没有方法约束的接口,因此所有数据类型都实现了空接口,进而空接口类型的变量可以初始化为任意数据类型。这样就类似于Python一样,解决了参数类型的限制。空接口本身也是一种数据类型!
func Println(a ...interface{}) (n int, err error) {return Fprintln(os.Stdout, a...)}
再比如,在定义映射的时候,由于数据类型的强制约束导致能定义的字段非常少,往往不能满足开发需求,采用空接口屏蔽数据类型的影响是一个比较简单的做法。
2.2.2. 空接口的应用
package mainimport "fmt"// 在函数传参中使用func viewEle(a interface{}) {fmt.Printf("%T %#v\n", a, a)}func main() {viewEle([]string{"a","b","c"})viewEle(false)viewEle(100)}
package mainimport "fmt"var m0 map[uint64]interface{} // 空接口作为map数据类型的valuefunc main() {m0 = make(map[uint64]interface{}, 10)m0[0] = "abc"m0[1] = [...]string{"张三", "李四"}m0[2] = []int{1, 2, 3, 4}m0[3] = falsefmt.Printf("%T %#v\n", m0, m0)}
2.2.3. 接口的断言
在开发中普遍存在多种数据类型都是实现了相同的接口,那么多种数据类型的实例都可以赋值给这个接口的变量,当我们需要将接口变量还原为原本的数据数据类型时,必须要通过断言判断后才能实现!如以下场景:
在空接口中可以传递任意数据类型的数据,当我们需要判断当前传进来的是哪种数据类型,则需要通过断言来实现,即判断传进来的参数属于哪种类型。断言有两种方式:
- 通过if条件判断,
v, ok := x.(Type)来判断是否是Type类型,如果是则ok为true,否则为false - 通过switch判断,
x.(type)专门用在switch语句种判断数据类型 ```go package main
import “fmt”
func f0(a interface{}) { //, ok := a.(string) //if ok { // fmt.Println(a, “是一个字符串”) //} else { // fmt.Println(a, “不是一个字符串”) //} // 上述方法可以简写为如下形式: if , ok := a.(string); ok { fmt.Println(a, “是一个字符串”) } else { fmt.Println(a, “不是一个字符串”) } }
func f1(a interface{}) { switch v := a.(type) { case string: fmt.Println(v, “是一个字符串”) case int8, uint8, int16, uint16, int32, uint32, int64, uint64, int: fmt.Println(v, “是一个整数”) case float32, float64: fmt.Println(v, “是一个浮点数”) case bool: fmt.Println(v, “是一个布尔值”) default: fmt.Println(v, “是其它数据类型”) } }
func main() { f0(“abc”) f0(false) f1(123456789) f1(uint64(123456789)) f1(“武汉加油!”) f1([]int{1, 2, 3, 4}) }
[root@heyingsheng studygo]# go run day06/03-interface/main.go abc 是一个字符串 false 不是一个字符串 123456789 是一个整数 123456789 是一个整数 武汉加油! 是一个字符串 [1 2 3 4] 是其它数据类型
<a name="cbuu8"></a>### 2.4. 接口的嵌套子接口可以调用父接口的方法,如 line:25 的statusManager实例化的对象可以调用接口 starter 和 stoper 的方法。要想实现子接口,则必须要实现父接口和子接口中所有的方法!<br />需要注意的是,在嵌套的接口中,两个父类接口中不能有重名的方法,比如这个案例中如果 starter 和 stoper 两个都要实现相同方法 query(),那么 startusManager 接口就会报错!```gopackage mainimport "fmt"type service struct {name string}func (s *service)start() {fmt.Printf("%s start!\n", s.name)}func (s *service)stop() {fmt.Printf("%s stop!\n", s.name)}type starter interface {start()}type stoper interface {stop()}type statusManager interface{ // 嵌套interface,包含了 starter 和 stoper 两个接口starterstoper}func main() {var app statusManager = &service{"httpd"}app.start() // statusManager 可以调用子接口 starer 的方法app.stop() // statusManager 可以调用子接口 stoper 的方法}
[root@heyingsheng day05]# go run 06-interface/main.gohttpd start!httpd stop!
3. 接口的使用场景和案例
3.1. 列表排序
以上描述的是接口的定义和使用语法,但是对初学者而言,最不好掌握的是接口的应用场景。通过案例来熟悉接口应用!
- 问题:定义结构体Student,其包含字段: Sid, Name, Age, Score;要求对
[]Student{}按照考试成绩 Score倒叙排列! - 思路:除了使用排序算法之外,Golang 的 sort 包中提供了
func Sort(data Interface)函数,可以对实现了Interface接口的data变量进行排序!Interface接口的定义如下:
构建Student结构体切片,并使用type Interface interface {// Len方法返回集合中的元素个数Len() int// Less方法报告索引i的元素是否比索引j的元素小Less(i, j int) bool// Swap方法交换索引i和j的两个元素Swap(i, j int)}
Sort()进行排序 ```go package main
import ( “fmt” “math/rand” “sort” )
type Student struct { Name string Score int }
type SliceStudent []*Student
func (s SliceStudent)Print() string { var res string for _, v := range s { res += fmt.Sprintf(“[name=%v, score=%v] “, v.Name, v.Score) } return res }
// 以下三个方法是提供给 sort.Sort() 函数使用 func (s SliceStudent)Len() int{ return len(s) }
func (s SliceStudent)Less(i, j int) bool { if s[i].Score > s[j].Score { return true } return false }
func (s SliceStudent)Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() { var s0 = make(SliceStudent, 0, 5) for i:=0; i<5; i++ { s0 = append(s0, &Student{fmt.Sprintf(“stud-%d”, i+1), rand.Intn(100)}) } fmt.Printf(“排序前:%v\n”, s0.Print()) sort.Sort(s0) fmt.Printf(“排序后:%v\n”, s0.Print()) }
```[root@heyingsheng studygo]# go run day06/03-interface/main.go排序前:[name=stud-1, score=81] [name=stud-2, score=87] [name=stud-3, score=47] [name=stud-4, score=59] [name=stud-5, score=81]排序后:[name=stud-2, score=87] [name=stud-1, score=81] [name=stud-5, score=81] [name=stud-4, score=59] [name=stud-3, score=47]
3.2. 对类型进行抽象
package taskimport "fmt"type Cluster struct {ID intName stringInfo string}// 构造函数func NewCluster(name, info string, id int) *Cluster {return &Cluster{ID: id,Name: name,Info: info,}}// Createfunc (c *Cluster) Create() {fmt.Printf("create cluster name:%s; id:%d\n", c.Name, c.ID)}// Deletefunc (c *Cluster) Delete() {fmt.Printf("delete cluster name:%s; id:%d\n", c.Name, c.ID)}// Getfunc (c *Cluster) GetSelf() interface{} {fmt.Printf("get cluster name:%s; id:%d\n", c.Name, c.ID)return c}
package taskimport "fmt"type Service struct {ID intName stringInfo string}// 构造函数func NewService(name, info string, id int) *Service {return &Service{ID: id,Name: name,Info: info,}}// Createfunc (s *Service) Create() {fmt.Printf("create service name:%s; id:%d\n", s.Name, s.ID)}// Deletefunc (s *Service) Delete() {fmt.Printf("delete service name:%s; id:%d\n", s.Name, s.ID)}
package task// 定义接口type Handler interface {Create()Delete()GetSelf() interface{}}
package deployimport "go_learn/day20/task"type Deployment struct {App task.Handler // 使用接口抽象 cluster 和 serviceState stringDesireState string}func NewDeployment(app task.Handler, state, desireState string) *Deployment {return &Deployment{App: app,State: state,DesireState: desireState,}}func (d *Deployment) Run() {d.App.Create()}func (d *Deployment) Delete() {d.App.Delete()}
package mainimport ("fmt""go_learn/day20/deploy""go_learn/day20/task")func main() {cluster := task.NewCluster("k3s","k3s info", 10001)service := task.NewService("nginx","nginx info", 10002)deploy.NewDeployment(cluster, "pending", "running").Run()deploy.NewDeployment(service,"pending","deleted").Delete()fmt.Println()fmt.Println(deploy.NewDeployment(cluster, "pending", "running").App.GetSelf().(*task.Cluster).Name)}
[root@duduniao go_learn]# go run day20/cmd/main.gocreate cluster name:k3s; id:10001delete service name:nginx; id:10002get cluster name:k3s; id:10001k3s
