作者 青鸟

GO语言中接口的基本概念

在官方文档中接口的定义如下:

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

我们传统的接口可能指的是程序对外的API,在GO语言中专门一个类型叫做接口。接口是一种抽象的类型,他是一组方法签名的集合,它把所有具有共性的方法定义在了一起,任何其他类型只要实现了这些方法就是实现了这个接口。我们可以定义一个结构体实现该接口所有方法。因此,接口就是定义了对象的行为。

接口的主要工作是仅提供由方法名称,输入参数和返回类型组成的方法签名集合。接口的方法的具体的实现是基于一个类型的。 由类型(例如struct结构体)来声明方法并实现它们。可以基于很多不同的类型实现这个接口。(同样,一个类型也可以实现多个接口)。

声明一个接口之后,要用某个特定的结构体类型来实例化这个接口。(前提:这个特定的结构体类型已经实现了该接口)
然后你就可以用这个接口调用该结构体类型上的所有方法。

如果不在类型上实现接口中的方法,那么接口仍然是一个概念

如果一个类型实现了在接口中定义的所有声明方法,则称该类型实现了该接口。


定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
/* variables */
}

/* 在结构体上实现接口方法 */
func (struct_variable_name struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}

注意:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。

另外对于接口的命名,我们使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。


实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
)

type Sayer interface {
Say()
}

//声明了一个 Cat 结构体
type Cat struct{}

//在 Cat 结构体上实现方法 Say()
func (c Cat) Say() { fmt.Println("喵喵喵") }

//声明了一个 Dog 结构体
type Dog struct{}

//在 Dog 结构体上实现方法 Say()
func (d Dog) Say() { fmt.Println("汪汪汪") }

func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := Cat{} // 实例化一个cat
b := Dog{} // 实例化一个dog
x = a // 可以把cat实例直接赋值给x
x.Say() // 喵喵喵
x = b // 可以把dog实例直接赋值给x
x.Say() // 汪汪汪
}

接口类型变量能够存储所有实现了该接口的实例。

接口的用处

我们面对重复的方法,将其抽象成一个集合,以此来简便我们的函数声明,我们只需要设计出具体的实现,而不需要反复的声明

空接口

Go语言中的空接口是接口类型的一种特殊的形式,即是一个没有任何方法的接口。因为,空接口没有任何方法,因此,我们可以说Golang中的任何数据类型都实现了空接口。空接口是任何类型的父接口。

使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。

**定义一个空接口var iname interface{}**。我们定义了一个空接口类型的变量 iname,该变量可以保存任何类型的变量。

案例

使用空接口,可以保存任何类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
"fmt"
)
func main() {
// 使用空接口,可以保存任何类型的变量
var any interface{}
any = 1024
fmt.Println("Any =", any)
any = 1024.1
fmt.Println("Any =", any)
any = "HaiCoder"
fmt.Println("Any =", any)
any = true
fmt.Println("Any =", any)
}

首先,我们定义了一个空接口类型的变量 any,接着,我们分别给空接口变量 any,赋值为 int 类型、float 类型、string 类型 和 bool 类型,并打印。

空接口类型的变量可以保存任何类型的变量,并能正确存储对应的值。

注意:从空接口中获取变量必须使用类型转换

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"fmt"
)
func main() {
// 从空接口获取变量,必须使用类型转
var any interface{}
any = 1024
fmt.Println("Any =", any)
var i int = any
fmt.Println("I =", i)
}

运行上述程序后会抛出错误,,因为空接口的类型不可以直接使用,必须要经过类型断言转换 之后,才可以使用。