接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
1.接口类型
1.1 接口类型的说明
Go语言中 接口(interface) 是一种抽象的类型。
接口(interface) 是一组 方法 的集合,是 duck-type programming(鸭子类型) 的一种体现,接口所做的事情就像是定义一个协议(规则),只要一台机器有洗衣和甩干的功能,就称之为洗衣机,不关心属性(数据),只关心行为(方法)。
1.2 接口类型的定义
Go语言提倡面向接口编程。
接口是一个或多个方法签名的集合。
任何类型的方法集中只要拥有该接口'对应的全部方法'签名。
就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。
这称为Structural Typing。
所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。
当然,该类型还可以有其他方法。
接口只有方法声明,没有实现,没有数据字段。
接口可以匿名嵌入其他接口,或嵌入到结构中。
对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
只有当接口存储的类型和对象都为nil时,接口才等于nil。
接口调用不会做receiver的自动转换。
接口同样支持匿名字段方法。
接口也可实现类似OOP中的多态。
空接口可以作为任何类型数据的容器。
一个类型可实现多个接口。
接口命名习惯以 er 结尾。
每个接口由数个方法组成,接口的定义格式如下:
1
2
3
4
5
|
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
|
其中:
1.接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
2.方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
举个例子:
1
2
3
|
type writer interface{
Write([]byte) error
}
|
这里定义了一个 writer 的 接口(interface),能够看到的就只是这个接口定义了一个 Write 方法,具体实现什么功能也不可知。
1.3 实现接口的条件
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
举个例子,这里我们定义一个 Phone 对象
1
2
3
4
|
type Phone interface {
Call()
SendMessage()
}
|
定义 OPPO 和 HUAWEI 两个结构体:
1
2
3
4
5
6
7
8
9
|
type OPPO struct {
Name string
Price float64
}
type HUAWEI struct {
Name string
Price float64
}
|
Phone 接口中有两个方法 Call 和 SendMessage 方法,因此需要给 OPPO 和 HUAWEI 实现 Call 和 SendMessage 方法就实现了 Phone 接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func (oppo OPPO) Call() {
fmt.Printf("%s 有打电话的功能 \n", oppo.Name)
}
func (oppo OPPO) SendMessage() {
fmt.Printf("%s 有发短信的功能 \n", oppo.Name)
}
func (huawei HUAWEI) Call() {
fmt.Printf("%s 有打电话的功能 \n", huawei.Name)
}
func (huawei HUAWEI) SendMessage() {
fmt.Printf("%s 有发短信的功能 \n", huawei.Name)
}
|
接口的实现就是这样,只要实现了接口中的所有方法,就实现了这个接口。
1.4 接口变量类型
接口类型变量能够存储所有实现了该接口的实例。如上 1.3 实现接口的条件 举例所示,Phone 类型的变量能够存储 HUAWEI 和 OPPO 类型的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func interfaceVariable() {
// 声明一个Phone 类型的变量x
var x GoInterface.Phone
// 实例化一个OPPO
findx6 := GoInterface.OPPO{
Name: "Find X6",
Price: 5999,
}
// 实例化一个HUAWEI
p30 := GoInterface.HUAWEI{
Name: "HUAWEI P30",
Price: 3999,
}
// 可以把OPPO实例直接赋值给x
x = findx6
x.Call()
// 可以把HUAWEI实例直接赋值给x
x = p30
x.Call()
}
|
2. 类型与接口之间的关系
2.1 一个类型实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,OPPO 手机可以打电话,也可以发短信。我们就分别定义 Caller 接口和 Message 接口,如下:
1
2
3
4
5
6
7
|
type Caller interface {
Call()
}
type Message interface {
SendMessage()
}
|
OPPO 既可以实现 Caller 接口,也可以实现 Message 接口。
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
|
type OPPO struct {
Name string
Price float64
}
// 实现Caller接口
func (oppo OPPO) Call() {
fmt.Printf("%s支持打电话功能\n", oppo.name)
}
// 实现Message接口
func (oppo OPPO) SendMessage() {
fmt.Printf("%s支持发短信功能\n", oppo.name)
}
func main() {
var x Caller
var y Message
var a = OPPO{Name: "Find X6", Price:5999,}
x = a
y = a
x.Call()
y.SendMessage()
}
|
2.2 多个类型实现同一接口
Go语言中不同的类型还可以实现同一接口,首先我们定义一个 Caller 接口,它要求必须由一个 Call 方法。
1
2
3
|
type Caller interface {
Call()
}
|
例如,HUAWEI 手机可以打电话,OPPO 也可以打电话。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type OPPO struct {
Name string
Price float64
}
type HUAWEI struct {
Name string
Price float64
}
// OPPO 类型实现Caller接口
func (oppo OPPO) Call() {
fmt.Printf("%s 有打电话的功能 \n", oppo.Name)
}
// HUAWEI 类型实现Caller接口
func (huawei HUAWEI) Call() {
fmt.Printf("%s 有打电话的功能 \n", huawei.Name)
}
|
并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// Phone
type Phone interface {
NFC()
Call()
}
// NFC模块
type NFCER struct{}
// 实现Phone接口的NFC()方法
func (nfc NFC) NFC() {
fmt.Println("NFC刷卡")
}
// OPPO手机
type OPPO struct {
NFCER //嵌入NFC模块
}
// 实现Phone接口的Call()方法
func (oppo OPPO) Call() {
fmt.Println("OPPO手机支持打电话功能")
}
|
3.空接口
3.1 空接口的定义
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func emptyInterface() {
// 定义一个空接口x
var x interface{}
s := "euansu.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
|
3.2 空接口的应用
空接口可以实现接口任意类型的函数参数。
1
2
3
4
|
// 空接口作为函数参数
func funcMethod(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
|
空接口可以作为 map 的值。
1
2
3
4
5
6
|
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
|