Go接口

一、接口的定义

  • 接口是一种抽象类型
  • 接口定义了方法的集合,简单地概括接口的作用:不关心是什么,只关心能做什么

二、接口的声明

声明一个接口很简单,使用interface关键字,然后定义接口包含哪些方法即可

1
2
3
4
type IF interface {
functionA()
functionB(string) string
}

接口的组合,类似结构体的组合

1
2
3
4
5
6
7
8
9
10
11
12
13
type IF interface {
functionA()
functionB(string) string
}

type IF2 interface {
functionC()
}

type IF3 interface {
IF
IF2
}

三、接口的实现

只要一个对象实现了某个接口的所有方法,那么就实现了这个接口,也可以称这个变量是该接口类型。或者说 一个具体类型 是一个(is-a) 特定的接口类型。例如,
*os.File是一个io.ReaderWriter

Example:

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
package main

import "fmt"

type IF interface {
getName() string
}

type Human struct {
fName, lName string
}

type Car struct {
model, vendor string
}

type Animal struct {
Name string
}

func (a Animal) getName() string {
return a.Name
}

func (h *Human) getName() string {
return h.fName + h.lName
}

func (c *Car) getName() string {
return c.model + c.vendor
}

func main() {
ifs := []IF{}
h := Human{fName: "w", lName: "e"}
c := new(Car)
c.model = "x5"
c.vendor = "bmw"
a := Animal{Name: "a cat"}
ifs = append(ifs, &h)
ifs = append(ifs, c)
ifs = append(ifs, a)
for _, v := range ifs {
fmt.Println(v.getName())
}
}

Example2:

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
package main

import "fmt"

type WeChatPay struct {
method string
cert string
}
type AliPay struct {
method string
apiKey string
}

func (w *WeChatPay) doPay(amount int) {
fmt.Println("使用微信支付, 通过cert处理")
fmt.Println("使用", w.method, "支付金额", amount)
}
func (a *AliPay) doPay(amount int) {
fmt.Println("使用支付宝支付, 通过apiKey处理")
fmt.Println("使用", a.method, "支付金额", amount)
}

type Payer interface {
doPay(int)
}

func Pay(p Payer, amount int) {
p.doPay(amount)
}

func main() {

wechat := WeChatPay{method: "微信支付", cert: "xxx.cert"}
alipay := AliPay{method: "支付宝支付", apiKey: "xxxxxx"}

Pay(&wechat, 50)
Pay(&alipay, 100)
}

四、接口值的组成

从概念上来讲,接口的值分为两个部分:

  • 一个具体的类型(称为动态类型)
  • 该类型的值(称为动态值)

对于Go这样的静态类型语言,类型仅仅是编译时的概念,并不是一个值

对于接口,是用类型描述符存储动态类型。然后动态值部分,是对应值的一个副本,即指向该值的一个指针

1
2
3
4
5
var w io.Writer        // 接口类型的零值为nil

w = os.Stdout // os.Stdout实现了io.Writer接口
w = new(bytes.Buffer) // bytes.Buffer实现了io.Writer接口
w = nil // 可以给接口赋nil

五、接口是可比较的

接口值是可比较的

1
2
3
4
5
6
7
8
var w1 io.Writer
var w2 io.Writer

fmt.Println(w1 == w2) // true

w1 = os.Stdout
w2 = new(bytes.Buffer)
fmt.Println(w1 == w2) // false

由于是可以比较的,因此,接口可以作为map的key,switch语句的操作数。

但是需要注意,如果动态类型一致,但是动态值本身是不可比较的,那么也无法将两个接口进行比较

1
2
3
var x interface{}
x = []int{1, 2, 3}
fmt.Println(x == x) // panic

六、含有空指针的非空接口

以下程序会导致panic

1
2
3
4
5
6
7
8
9
10
11
12
13

main(){
var buf *bytes.Buffer
fmt.Printf("%v\n", buf)
f(buf)
}

func f(out io.Writer) {
if out != nil {
out.Write([]byte("done\n")) // panic
}
}

原因是,out接口的类型不为空而值为空,此时接口的值是不等于nil的。

出现panic是因为(*bytes.Buffer).Write 的接收者不能为空;正确的写法如下

1
2
3
4
5
main(){
var buf io.Writer
buf = new(bytes.Buffer)
f(buf)
}

七、接口排序

接口的排序主要是使用sort.Interface

一个内置的排序算法需要知道以下内容:

  • 序列的长度
  • 元素比较的结果
  • 交换两个元素的方式
    1
    2
    3
    4
    5
    6
    7
    8
    package sort

    type Interface interface{
    Len() int // 元素数量
    Less(i, j int) bool // i, j是元素的下标
    Swap(i, j int) // 交换元素位置
    }

常用的排序方法

sort包里面默认实现了一些常用类型的排序

字符串排序

使用sort.StringSlice类型

1
2
names := sort.StringSlice{"1", "3", "2"}
sort.Sort(names)

或者使用更简便的sort.Strings函数

1
2
names := []string{"1", "3", "2"}
sort.Strings(names)

整型排序

1
2
3
ages := []int{18, 17, 19}
sort.Ints(ages)

浮点型排序

1
2
scores := []float64{80.5, 75.0, 99.5}
sort.Float64s(scores)

实现字符串切片排序实现

我们可以自己定义一个类型,来实现字符串切片的排序

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
package main

import (
"fmt"
"sort"
)

type MyStringList []string

func (m MyStringList) Len() int {
return len(m)
}
func (m MyStringList) Less(i, j int) bool {
return m[i] < m[j]
}
func (m MyStringList) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func main() {
names := []string{
"3.cx",
"4.dx",
"2.bx",
"1.ax",
"1.bx",
}
// 只需要将[]string 转成MyStringList类型即可。
// 注意,类型转换生成了一个新的slice,
// 与原始的names有同样的长度、容量和底层数组。不同的就是额外增加了三个用于排序的方法。
sort.Sort(MyStringList(names))
for _, v := range names {
fmt.Printf("%s\n", v)
}
}

输出结果:

1
2
3
4
5
1.ax
1.bx
2.bx
3.cx
4.dx