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