正式入坑go,感觉接口这里遇到了点阻碍,记录一下。
主要问题包括:接口定义,接口与多态,空接口,类型断言
一、概念
在Go中,接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口(即go语言的接口是 非侵入式 的,只要实现了接口中的所有方法,就实现了这个接口,不需要额外声明)。接口指定类型应具有的方法,类型决定如何实现这些方法。
简单来说,接口是一组仅包含 方法名,参数,返回值 的未具体实现的方法集合,就是只定义了一个对象的行为规范(方法),只定义规范不实现,由具体的对象来实现规范的细节。
接口就是一种特殊的类型,并且类似结构体,亦可以继承(嵌套)
二、接口代码实现
//定义接口
type interfaceName interface {
// 方法列表
mothod_name1( parameter1...) (return_type1...)
mothod_name2( parameter1...) (return_type1...)
...
}
-
当我们看到一个接口类型的值时,我们不了解它具体有什么内容,只知道通过它的方法能做什么
-
当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
-
方法列表中参数名和变量名可以省略
-
命名接口时,一般会在单词后面添加er,如有写操作的接口叫writer,有字符串功能的接口叫stringer等
接口与多态
多态指代码可以根据类型的具体实现采取不同行为的能力。如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值,下面例子即有两个类型实现了notifier
接口。
在下面代码中,因为notifier
接口里只有一个notify
方法,所以我们只需要给user
和admin
分别实现notify
方法就可以实现notifier
接口了。
// 使用接口展示多态行为
package main
import "fmt"
// 定义接口 接口的命名一般采用er结尾
type notifier interface {
notify()
}
// 用户信息
type user struct {
name string
email string
}
// notify()方法使用指针接收者实现了notifier接口,即方法的实现1
// 打印用户姓名和邮件信息
func (u *user) notify() {
fmt.Printf("Sending user email to %s <%s> \n", u.name, u.email)
}
// 管理者信息
type admin struct {
name string
email string
}
// notify()方法使用指针接收者实现了notifier接口,即方法的实现2
// 打印管理者姓名和邮件信息
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s <%s> \n", a.name, a.email)
}
func main() {
// 创建结构体user的值传入sendNotification函数,
amy := user{"艾米", "[email protected]"}
xiashe := admin{"侠奢", "[email protected]"}
// 体现多态:接受不同变量
sendNotification(&amy)
sendNotification(&xiashe)
}
// 函数接收 接口类型, 接口类型变量能够存储所有实现了该接口的实例,即多态函数
// 函数接受了一个实现notifier接口的值
func sendNotification(n notifier) {
n.notify()
}
接口体现多态的第二种形式,接口类型变量 能够存储所有实现了该接口的实例, 例如下面的代码中,notifier
类型的变量能够存储user
和admin
类型的变量。
var a [2]notifier
amy = user{"艾米", "[email protected]"}
xiashe = admin{"侠奢", "[email protected]"}
a[0] = &amy
a[1] = &xiashe
此外,一个类型也可以实现多个接口
三、空接口与类型断言
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量
func main() {
// 定义一个空接口x
var x interface{}
//空接口存储string变量
a := "你好哇"
x = a
fmt.Printf("type:%T value:%v\n", x, x)
//存储int类型
b := 10086
x = b
fmt.Printf("type:%T value:%v\n", x, x)
//存储bool类型
c := true
x = c
fmt.Printf("type:%T value:%v\n", x, x)
}
空接口的应用
- 使用空接口实现可以接收任意类型的函数参数。
// 空接口作为传入的函数参数
// 该实例打印出传入类型和参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
- 使用空接口实现可以保存任意值的映射(map)
// 空接口作为map值
// 传入map值不确定时可以使用,但是map键不可以
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "侠奢"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
类型断言
既然空接口可以存储任意类型,那么如何区分不同的类型?
这里涉及到动态类型和动态值的问题...就此略过
下面写一个具体例子理解一下类型断言的操作
type writer interface{}
...
var x Writer
x = os.Stdout
x = new(bytes.Buffer)
x = nil
x = true
由上面代码可知,空接口可以传入多种不同类型,怎么判断上面x的类型呢?
我们可以用以下格式判断,x代表接口类型的变量,T代表可能的类型。
x.(T)
下面写具体判别方法,主要有两种判别方法
- 第一种,if-else
func main() {
var x interface{}
x = "你好啊"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}
- 第二种,switch方法
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type!")
}
}
最后搬运一个比较完整的断言例子
package main
import (
"fmt"
)
// 定义一个结构体
type Student struct {
Name string
}
// 类型断言
func main() {
Params := make([]interface{}, 3) //定义接口类型,较为常用
Params[0] = 88 // 整型
Params[1] = "社会青年" // 字符串
Params[2] = Student{Name: "cbs"} // 自定义结构体类型
// Comma-ok断言
for index, v := range Params {
if _, ok := v.(int); ok {
fmt.Printf("Params[%d] 是int类型 \n", index)
} else if _, ok := v.(string); ok {
fmt.Printf("Params[%d] 是字符串类型\n", index)
} else if _, ok := v.(Student); ok {
fmt.Printf("Params[%d] 是自定义结构体类型\n", index)
} else {
fmt.Printf("list[%d] 未知类型\n", index)
}
}
// switch判断,这种比较OK
for index, v := range Params {
switch value := v.(type) {
case int:
fmt.Printf("Params[%d] 是int类型, 值:%d \n", index,value)
case string:
fmt.Printf("Params[%d] 是字符串类型, 值:%s\n", index,value)
case Student:
fmt.Printf("Params[%d] 是Person类型, 值:%s\n", index,value)
default:
fmt.Printf("list[%d] 未知类型\n", index)
}
}
}