在 Go 语言中,接口就是方法签名(Method Signature)的集合,接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
# 接口定义
使用type
关键字来定义接口,格式:
type ObejctName interface {
method()
}
1
2
3
2
3
定义电话接口,里面包含call()
方法
type phone interface {
call()
send(message string)
}
1
2
3
4
2
3
4
# 实现接口
type XiaoMi struct {
name string
}
// 函数
func Call(phone Phone) {
phone.send("消息")
}
// XiaoMi 实现 Phone 的方法
func (phone XiaoMi) call() {
fmt.Printf("我是一台 %s 手机,我能打电话 \n", phone.name)
}
func (phone XiaoMi) send(message string) {
fmt.Printf("我能发送 %s\n", message)
}
func main() {
xiaoMi := XiaoMi{name: "小米"}
xiaoMi.call()
xiaoMi.send("消息")
// 调用函数,传xiaoMi,因为 XiaoMi实现了Phone的方法,所以XiaoMi也是一个电话
Call(xiaoMi)
}
// 我是一台 小米 手机,我能打电话
// 我能发送 消息
// 我能发送 消息
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
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
# 接口实现多态
鸭子类型(Duck typing)的定义是,只要你长得像鸭子,叫起来也像鸭子,那我认为你就是一只鸭子
举个例子,我先定义商品(Goods)接口,意思是一个类型或者结构体,其中具有一些方法,getPrice()
和 orderInfo()
两个方法,那么我们可以理解为,只要实现了这两个方法的类型/结构体就可以称之为一个商品
- 定义一个
Goods
接口,里面有getPrice
type Good interface {
getPrice() int
orderInfo() string
}
1
2
3
4
2
3
4
- 定义两个结构体,分别是
Computer
和FreeGift
type Computer struct {
name string
quantity int
price int
}
type FreeGift struct {
name string
quantity int
price int
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 实现
Goods
的两个方法,Computer
和FreeGift
都算是商品Goods
了
// Computer
func (computer Computer) getPrice() int {
return computer.quantity * computer.price
}
func (computer Computer) orderInfo() string {
return "您要购买" + strconv.Itoa(computer.quantity) + "个" +
computer.name + "计:" + strconv.Itoa(computer.getPrice()) + "元"
}
// FreeGift
func (gift FreeGift) getPrice() int {
return 0
}
func (gift FreeGift) orderInfo() string {
return "您要购买" + strconv.Itoa(gift.quantity) + "个" +
gift.name + "计:" + strconv.Itoa(gift.getPrice()) + "元"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 对商品
Computer
和FreeGift
进行实例化
computer := Computer{
name: "computer",
quantity: 1,
price: 8000,
}
earphones := FreeGift{
name: "耳机",
quantity: 1,
price: 200,
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 创建一个购物车,(也就是类型为 Good的切片),来存放这些商品
goods := []Goods{computer, earphones}
1
- 并调用方法计算最终的价格
func calculateAllPrice(goods []Goods) int {
var allPrice int
for _, good := range goods {
fmt.Println(good.orderInfo())
allPrice += good.getPrice()
}
return allPrice
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
完整代码如下:
package main
import (
"fmt"
"strconv"
)
// 定义一个接口
type Goods interface {
getPrice() int
orderInfo() string
}
type Computer struct {
name string
quantity int
price int
}
// 实现Goods接口
func (computer Computer) getPrice() int {
return computer.quantity * computer.price
}
func (computer Computer) orderInfo() string {
return "您要购买" + strconv.Itoa(computer.quantity) + "个" +
computer.name + "计:" + strconv.Itoa(computer.getPrice()) + "元"
}
type FreeGift struct {
name string
quantity int
price int
}
func (gift FreeGift) getPrice() int {
return 0
}
func (gift FreeGift) orderInfo() string {
return "您要购买" + strconv.Itoa(gift.quantity) + "个" +
gift.name + "计:" + strconv.Itoa(gift.getPrice()) + "元"
}
// 计算价格方法
func calculateAllPrice(goods []Goods) int {
var allPrice int
for _, good := range goods {
fmt.Println(good.orderInfo())
allPrice += good.getPrice()
}
return allPrice
}
func main() {
// 实例化
computer := Computer{
name: "computer",
quantity: 1,
price: 8000,
}
earphones := FreeGift{
name: "耳机",
quantity: 1,
price: 200,
}
goods := []Goods{computer, earphones}
allPrice := calculateAllPrice(goods)
fmt.Printf("该订单总共需要支付 %d 元", allPrice)
}
// 您要购买1个computer计:8000元
// 您要购买1个耳机计:0元
// 该订单总共需要支付 8000 元
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 类型断言
Type Assertion(中文名叫:类型断言),作用如下:
- 判断类型是否为
nil
- 判断类型是否为某个具体类型
使用方式有两种:
第一种:
t := i.(T)
可以断言一个接口对象
i
里面是不是nil
,i
代表接口对象,T
代表接口对象存储的值类型,如果断言成功,返回其类型t
,断言失败,触发panic
func triggerPanicOne() {
var i interface{} = 10
t1 := i.(int)
fmt.Println(t1)
t2 := i.(string)
fmt.Println(t2)
}
// 10
// panic: interface conversion: interface {} is int, not string
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 第二种:
t, ok:= i.(T)
,断言成功,返回其类型t
,ok
值为true
,断言失败,返回其类型的零值t
,ok
值为false
func main() {
var i interface{} = 10
t1, ok := i.(int)
fmt.Printf("%d-%t\n", t1, ok)
t1, ok := i.(string)
fmt.Printf("%d-%t\n", t1, ok)
}
// int零值是0,string的零值是"",interface{}的零值是<nil>
// 10-true
// -false
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 结合
switch
来判断,switch i.(type)
,注意,匹配到了是
func useSwitchToTypeAssertion(i interface{}) {
switch x := i.(type) {
case int:
fmt.Println(x, "is int")
case string:
fmt.Println(x, "is string")
case nil:
fmt.Println(x, "is nil")
default:
fmt.Println(x, "not type matched")
}
}
func main() {
useSwitchToTypeAssertion("10")
useSwitchToTypeAssertion("abc")
useSwitchToTypeAssertion(nil)
useSwitchToTypeAssertion(10.01)
}
// 10 is string
// abc is string
// <nil> is nil
// 10.01 not type matched
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 空接口
空接口,即定义的接口里面不包含任何方法,可以说所有的类型至少都实现了空接口
type emptyInterface interface {
}
1
2
2
空接口的值和类型都是<nil>
func main() {
var i interface{}
fmt.Printf("type: %T, value: %v", i, i)
}
// type: <nil>, value: <nil>
1
2
3
4
5
6
2
3
4
5
6
空接口的使用
- 用于作为实例承载任意类型的值
func creatEmptyInterface() {
var i interface{}
i = 5
fmt.Println(i)
i = "string"
fmt.Println(i)
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 用于函数接收任意类型的值
func main {
a := 5
b := "string"
// 接收单个值
receiveEmptyInterface(a)
receiveEmptyInterface(b)
// 接收多个值
receiveMoreEmptyInterface(a, b)
}
func receiveEmptyInterface(emptyInterface interface{}) {
fmt.Println(emptyInterface)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 可以用于定义接收任意类型的
array
、slice
、map
、strcut
func receiveMoreTypeEmptyInterface() {
any := make([]interface{}, 5)
any[0] = 11
any[1] = "hello world"
any[2] = []int{11, 22, 33, 44}
fmt.Println("创建一个切片,接收多种类型的值")
for _, value := range any {
fmt.Println(value)
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
注意:
- 空接口可以承载任意值,但不代表任意类型就可以承接空接口类型的值
- 当空接口承载数组和切片后,该对象无法再进行切片
- 当你使用空接口来接收任意类型的参数时,它的静态类型是 interface{},但动态类型(是 int,string 还是其他类型)我们并不知道,因此需要使用类型断言
# 接口的三个潜规则
# 方法调用的限制
定义一个Animal
接口,其中包含eat()
方法,定义了Cat
接口体,包含了的两个方法eat()
、run()
type Animal interface {
eat()
}
type Cat struct {
name string
}
func (cat Cat) eat() {
fmt.Printf("%s is animal, it cant eatting.\n", cat.name)
}
func (cat Cat) run() {
fmt.Printf("%s is animal, it can running.\n", cat.name)
}
func printType(i interface{}) {
switch i.(type) {
case int:
fmt.Println("参数的类型是 int")
case string:
fmt.Println("参数的类型是 string")
}
}
func main() {
// 显示申明了 animal对象为Animal接口,
var animal Animal
animal = Cat{name: "cat"}
animal.eat()
// 会报错,所调用方法受到接口的方法限制
// animal.run()
// 更改
animal1 := Cat{name: "cat"}
animal1.eat()
animal1.run()
}
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
32
33
34
35
36
37
38
39
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
32
33
34
35
36
37
38
39
# 调用函数时的隐式转换
Go 语言中的函数调用都是值传递的,变量会在方法调用前进行类型转换
func printType(i interface{}) {
switch i.(type) {
case int:
fmt.Println("参数的类型是 int")
case string:
fmt.Println("参数的类型是 string")
}
}
func main() {
a := 10
// 隐式转换了 a的值 为 空接口类型
printType(a)
// 直接判断就会报错
// switch a.(type) {}
// 调整为 显示转换
switch interface{}(a).(type){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 类型断言中的隐式转换
静态类型为接口类型
的对象才可以进行类型断言
func main(){
var a interfaceP{} = 5
// 对静态类型a断言完成后,go 隐式转换返回了一个静态类型
swtich b := a.(type){
case int:
// 这里会报错,因为b此时是静态类型,不是接口类型了,不能再进行断言
b.(int)
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9