Go笔记 - interface

447 阅读4分钟

interface就是接口的意思。从名字可以推测有:对外,适配的含义。Go的接口是一系列函数的集合,如果一个类型实现了该接口所有函数,则说该类型实现了该接口。Go中的接口有什么用呢?可以实现duck typing。通过接口可以实现类似面向对象 语言中的多态特性,接口类比成基类,实现接口的类型类比成派生类。在Go中,接口被广泛使用,通过定义一些公共的接口,能够给编码实现带来极大的便利。Go强调接口应该小巧,通过多个小接口组合成大接口,这与Go强调组合的理念不谋而合。顺带说一下:Go没有继承。

下面看一个有关接口的例子:

package main

import (
	"fmt"
)

type integer interface {
	add(int)
	show()
}

type num struct {
	a int
}

func (n *num) add(i int) {
	n.a += i
}

func (n num) show() {
	fmt.Println(n.a)
}

func show(n integer) {
	n.show()
}

func main() {
	n := num{a: 0}
	n.add(1)
	show(&n) // 输出: 1
    show(n) // 报错
}

当我试图使用show(n)打印变量n的值时,会报错:

# command-line-arguments
./a.go:31:6: cannot use n (type num) as type integer in argument to show:
	num does not implement integer (add method has pointer receiver)

上面的意思是说,以类型作为参数传递时,并没有实现integer接口的add方法。先直接说结论:

T类型方法集 *T类型方法集
T类型参数 YES NO
*T类型参数 YES YES

为什么T类型参数不包含*T类型方法集呢?因为T类型参数可以是临时变量,一个临时变量可能在内存中并没有具体的地址,无法进行引用。而*T类型参数必定能进行解引用获取到变量,传递其值类型,所以包含T类型方法集

空接口

Go中有一个特殊的接口类型。空接口类型:interface{}。在Go中所有的类型都自动实现了空接口类型(有点像C中的void类型)。下面看一个例子:

package main

import (
	"fmt"
)

type integer interface {
	add(int)
	show()
}

type num struct {
	a int
}

func (n *num) add(i int) {
	n.a += i
}

func (n num) show() {
	fmt.Println(n.a)
}

func show(n interface{}) {
	realN, _ := n.(integer)
	realN.show()
}

func main() {
	n := num{a: 0}
	n.add(1)
	show(&n)
}

上面的例子仅是修改了show()函数的参数类型为interface{}类型,通过类型断言获得到真实的类型,实现相同的功能。但使用了interface{}参数类型,编译器对类型检查会变弱。上面调用show(n)函数,编译器不会报错,而是造成运行时恐慌。所以,即使interface{}类型看上去是万能的类型,但会削弱编译器的类型检查能力,很容易产生bug。

内嵌结构体

内嵌结构体是Go组合理念的体现。通过内嵌的方式,将不同类型的数据成员方法集组合在一起。通过成员和方法提升的方式,实现类似继承的特性(Go没有继承,内嵌结构体不是继承)。下面看一个例子:

package main

import (
	"fmt"
)

type integer interface {
	add(int)
	show()
}

type num struct {
	a int
}

type bigNum struct {
	num
	b int
}

func (n *num) add(i int) {
	n.a += i
}

func (n num) show() {
	fmt.Println(n.a)
}

func show(n integer) {
	n.show()
}

func main() {
	n := bigNum{num: num{a: 0}, b: 0}
	n.add(1)
	show(&n) // 输出:1
}

上的例子中,bigNum结构体内嵌了num结构体,此时bigNum自动实现了integer接口。所以,show()函数能接收*bigNum类型变量n作为参数。能够实现该特性主要依赖于成员和方法提升内嵌结构体内部的成员和方法被提升到外部,在例子中就是num->bigNum提升也有其规律,如果外部存在同名变量或方法(无论参数或返回值是否相同)则内部的不会被提升

内嵌结构体包括内嵌类型和指针类型的结构体。它们有什么区别呢?先直接放结论:(假设外部结构体为: O, 内嵌结构体为: T)

T类型方法集 *T类型方法集
内嵌T类型 O能调试用该类型方法集:YES
*O能调试用该类型方法集:YES
O能调试用该类型方法集:NO
*O能调试用该类型方法集:YES
内嵌*T类型 O能调试用该类型方法集:YES
*O能调试用该类型方法集:YES
O能调试用该类型方法集:YES
*O能调试用含该类型方法集:YES

下面看一个示例代码加深印象:

package main

import (
	"fmt"
)

type integer interface {
	add(int)
	show()
}

type num struct {
	a int
}

type bigNum struct {
	num
	b int
}

type smallNum struct {
	*num
	b int
}

func (n *num) add(i int) {
	n.a += i
}

func (n num) show() {
	fmt.Println(n.a)
}

func show(n integer) {
	n.show()
}

func main() {
	bn := bigNum{num: num{a: 0}, b: 0}
	sn := smallNum{num: &num{a: 0}, b: 0}
	// show(bn) // 报错
	show(&bn)

	show(sn)
	show(&sn)
}

调用show(bn)会报错,说bigNum类型内有实现add方法。通过内嵌T类型的方式,并不会暴露*T类型方法集给外部O类型的变量。