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:21
1. 使用满足接口的结构体初始化该变量。line:23 line:25
1. 使用该变量执行接口中的方法。line24 line26
```go
package main
import "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 backuper
fmt.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/redis
MySQL 备份完毕!
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 既可以初始化为结构体指针,也可以初始化为结构体。之所以可以初始化为指针,是因为接收者为值类型的方法存在语法糖,会自动对指针取值。
```go
package main
import (
"fmt"
"strings"
)
type service struct {
name string
port 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(),正常返回。语法糖
fmt.Println(strings.Repeat("-",30))
nginx := &service{
name: "Nginx",
port: 80,
}
nginx.manager("stop") // 结构体指针 调用自己的方法,不走接口,正常返回。语法糖
}
[root@heyingsheng day05]# go run 04-value/main.go
sshd 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 main
import (
"fmt"
)
type service struct {
name string
port 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 main
import "fmt"
// 在函数传参中使用
func viewEle(a interface{}) {
fmt.Printf("%T %#v\n", a, a)
}
func main() {
viewEle([]string{"a","b","c"})
viewEle(false)
viewEle(100)
}
package main
import "fmt"
var m0 map[uint64]interface{} // 空接口作为map数据类型的value
func main() {
m0 = make(map[uint64]interface{}, 10)
m0[0] = "abc"
m0[1] = [...]string{"张三", "李四"}
m0[2] = []int{1, 2, 3, 4}
m0[3] = false
fmt.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 接口就会报错!
```go
package main
import "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 两个接口
starter
stoper
}
func main() {
var app statusManager = &service{"httpd"}
app.start() // statusManager 可以调用子接口 starer 的方法
app.stop() // statusManager 可以调用子接口 stoper 的方法
}
[root@heyingsheng day05]# go run 06-interface/main.go
httpd 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 task
import "fmt"
type Cluster struct {
ID int
Name string
Info string
}
// 构造函数
func NewCluster(name, info string, id int) *Cluster {
return &Cluster{
ID: id,
Name: name,
Info: info,
}
}
// Create
func (c *Cluster) Create() {
fmt.Printf("create cluster name:%s; id:%d\n", c.Name, c.ID)
}
// Delete
func (c *Cluster) Delete() {
fmt.Printf("delete cluster name:%s; id:%d\n", c.Name, c.ID)
}
// Get
func (c *Cluster) GetSelf() interface{} {
fmt.Printf("get cluster name:%s; id:%d\n", c.Name, c.ID)
return c
}
package task
import "fmt"
type Service struct {
ID int
Name string
Info string
}
// 构造函数
func NewService(name, info string, id int) *Service {
return &Service{
ID: id,
Name: name,
Info: info,
}
}
// Create
func (s *Service) Create() {
fmt.Printf("create service name:%s; id:%d\n", s.Name, s.ID)
}
// Delete
func (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 deploy
import "go_learn/day20/task"
type Deployment struct {
App task.Handler // 使用接口抽象 cluster 和 service
State string
DesireState 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 main
import (
"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.go
create cluster name:k3s; id:10001
delete service name:nginx; id:10002
get cluster name:k3s; id:10001
k3s