1. 使用接口的原因
我们来看一段代码:
1 | type Cat struct{} |
上述代码中定义了狗 Dog 和猫 Cat 以及它们对应的方法 Say(),若要继续添加鸭 Duck 方法和其对应的方法 Say(),重复代码会非常多。我们可不可以从这三种动物类型中抽象出方法 Say(),这就是接口的作用,接口可以定义抽象方法,来规范我们的代码。
2. 接口的定义和使用
Go 语言非传统面对对象语言,没有类和继承的概念。但在 Go 语言中可以通过接口来实现面对对象的一些特性,接口是用来描述对象行为的,可以在其中定义一些抽象方法,这些方法不能被实现,同时接口中不能包含变量。
Go 语言中接口 interface 是一种抽象类型。
接口的定义格式:
1 | type Namer interface { |
一般接口以 [e]r 结尾,例如 Writer、Logger 等。若 er 结尾不合适时,可以采用 able 结尾,例如 Recoverable。
举个例子:
1 | type Writer interface { |
若一个对象实现了接口的所有方法,那么就实现了这个接口。
我们来定义一个 Sayer 接口:
1 | type Sayer interface { |
定义 dog 和 dat 两个结构体:
1 | type dog struct{} |
让 dog 和 cat 实现接口 Sayer 的 say 方法:
由于
Sayer接口中只有一个say方法,所以dog和cat实现了Sayer接口。
1 | func (d dog) say() { |
接口变量可以接收任何实现了该接口的实例,如:
1 | func main() { |
上述代码运行结果为:
1 | 喵喵喵 |
3. 值接收和指针接收实现接口的区别
现在有一个 Sayer 接口和 Cat 结构体:
1 | type Sayer interface { |
3.1 值接收实现接口
1 | func (c cat) say() { |
创建值实例 c 和指针实例 c2,然后通过 Sayer 接口变量 sayer 来调用方法 say():
1 | func main() { |
上述代码运行结果为:
1 | 喵喵喵 |
从上述例子中,我们可以发现:当以值接收形式实现接口时,不论是值实例还是指针实例,都可以赋值给接口变量。
3.2 指针接收实现接口
我们来看下指针接收来实现接口:
1 | func (c *cat) say() { |
运行后,编译器会提示 cat does not implement Sayer (say method has pointer receiver) 错误,因为实现 Sayer 接口的是 *cat,所以不能将 cat 的实例 c 赋值给接口变量 sayer ,也就是说 sayer 只能接收指针实例。
4. 类型和接口的关系
4.1 类型和接口的关系
一个类型实现多个接口
一个类型可以实现多个接口,且这些接口相互独立。动物既会叫,也会动,可以定义两个接口,然后让 cat 分别实现这两个接口:
1 | package main |
上述代码运行结果为:
1 | 喵喵喵 |
多个类型实现同一接口
一个接口可以被多个不同类型实现,现在有一个接口 Mover,猫 cat 和狗 dog 都会动,让它们分别实现 Mover 接口:
1 | package main |
上述代码运行结果为:
1 | 小猫咪溜了 |
一个接口的方法不一定要由一个类型完全实现,可以通过在类型中嵌入其他类型来实现,如:
1 | // WashingMachine 洗衣机 |
5. 接口嵌套
接口可以通过嵌套生成新的接口,如:
1 | // Sayer 接口 |
嵌套接口使用方式与普通接口一样,我们让 cat 来实现 animal 接口:
1 | func (c cat) say() { |
上述代码运行结果为:
1 | 喵喵喵 |
6. 空接口
6.1 空接口的定义
空接口是指未定义任何方法的接口,因此任何类型都实现了空接口(空接口有点类似于 Java 中的基类 Object)。
空接口类型变量可以存储任意类型的值:
1 | package main |
上述代码运行结果为:
1 | 空接口 |
6.2 空接口的应用
函数传参
使用空接口可以接收任意类型的参数:
1 | func show(x interface{}) { |
Map 的值
使用空接口可以实现保存任意类型的值的 Map:
1 | var studentInfo = make(map[string]interface{}) |
7. 类型断言
我们如何获取空接口变量的值和具体类型呢?可以采用类型断言来实现。
一个接口的值是由 具体类型 和 具体类型的值 组成的,分别称为接口的 动态类型 和 动态值。
我们来看一个例子:
1 | var w io.Writer |
来看下接口变量 w 的变化:

若要判断空接口类型变量的值,可以使用类型断言,其格式为:
1 | x.(T) |
x为空接口类型变量T表示断言x可能的类型
上述断言语句将返回两个参数,第一个参数为 x 转化为 T 类型后的值,第二个值为布尔型,若为 true 表明断言成功,若为 false 表明断言失败。
我们来看一个例子:
1 | func main() { |
若要进行多次断言,可以采用 switch 语句:
1 | func justifyType(x interface{}) { |
注意:当有多个类型存在相同的方法时才适合使用接口,不要为了刻意使用接口而使用接口,这样会增加运行时的损耗。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 richffan@outlook.com