Go接口

4/20/2021 go

在 Go 语言中,接口就是方法签名(Method Signature)的集合,接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法

# 接口定义

使用type关键字来定义接口,格式:

type ObejctName interface {
    method()
}
1
2
3

定义电话接口,里面包含call()方法

type phone interface {
    call()
    send(message string)
}
1
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

# 接口实现多态

鸭子类型(Duck typing)的定义是,只要你长得像鸭子,叫起来也像鸭子,那我认为你就是一只鸭子

举个例子,我先定义商品(Goods)接口,意思是一个类型或者结构体,其中具有一些方法,getPrice()orderInfo() 两个方法,那么我们可以理解为,只要实现了这两个方法的类型/结构体就可以称之为一个商品

  • 定义一个Goods接口,里面有getPrice
type Good interface {
    getPrice() int
    orderInfo() string
}
1
2
3
4
  • 定义两个结构体,分别是ComputerFreeGift
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
  • 实现Goods的两个方法,ComputerFreeGift都算是商品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
  • 对商品ComputerFreeGift进行实例化
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
  • 创建一个购物车,(也就是类型为 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
完整代码如下:
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

# 类型断言

Type Assertion(中文名叫:类型断言),作用如下:

  • 判断类型是否为nil
  • 判断类型是否为某个具体类型

使用方式有两种:

  • 第一种:t := i.(T)

    可以断言一个接口对象i里面是不是nili代表接口对象,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
  • 第二种:t, ok:= i.(T),断言成功,返回其类型tok值为true,断言失败,返回其类型的零值tok值为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
  • 结合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

# 空接口

空接口,即定义的接口里面不包含任何方法,可以说所有的类型至少都实现了空接口

type emptyInterface interface {
}
1
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

空接口的使用

  • 用于作为实例承载任意类型的值
func creatEmptyInterface() {
	var i interface{}
	i = 5
	fmt.Println(i)
	i = "string"
	fmt.Println(i)
}
1
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
  • 可以用于定义接收任意类型的arrayslicemapstrcut
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

注意:

  • 空接口可以承载任意值,但不代表任意类型就可以承接空接口类型的值
  • 当空接口承载数组和切片后,该对象无法再进行切片
  • 当你使用空接口来接收任意类型的参数时,它的静态类型是 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

# 调用函数时的隐式转换

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

# 类型断言中的隐式转换

静态类型为接口类型的对象才可以进行类型断言

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

# 练习源码

Interface (opens new window)