本节介绍使用接口时遇到的一些常见问题以及它的设计与实现,包括接口的类型转换、类型断言以及动态派发机制
概述
在计算机科学中,接口是计算机系统中多个组件共享的边界,不同的组件能够在边界上交换信息1。如下图所示,接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口。
关键字:组件共享的边界、中间层
- 解耦
例如:
有上下游关系的解耦:可移植操作系统接口(操作系统来实现),让计算机软件可移植
- 隐藏实现,减少关注点
代码必须能够被人阅读,只是机器恰好可以执行
例如:
使用SQL不需要关注底层数据库的具体实现
隐式接口
Golang中的接口的实现都是隐式的。例如:
type error interface {
Error() string
}
type RPCError struct {
Code int64
Message string
}
func (e *RPCError) Error() string {
return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
}
只需要实现了Error() string 方法,就实现了error接口
我们使用上述 RPCError 结构体时并不关心它实现了哪些接口,Go 语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查。
类型
接口也是 Go 语言中的一种类型,它能够出现在变量的定义、函数的入参和返回值中并对它们进行约束,不过 Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的 interface{}:
- runtime.iface
- runtime.eface : 不包含任何方法的接口 interface{}
注意:
与 C 语言中的 void 不同,interface{} 类型*不是任意类型。如果我们将类型转换成了 interface{} 类型,变量在运行期间的类型也会发生变化,获取变量类型时会得到 interface{}
package main
func main() {
type Test struct{}
v := Test{}
Print(v)
}
func Print(v interface{}) {
println(v)
}
上述函数不接受任意类型的参数,只接受 interface{} 类型的值,在调用 Print 函数时会对参数 v 进行类型转换,将原来的 Test 类型转换成 interface{} 类型,本节会在后面介绍类型转换的实现原理。
当接口的实现者为结构体,那么结构体就会在方法调用时发生复制
当接口的实现者为指针,那么指针就会在方法调用时进行复制
type Cat struct{}
func (c Cat) Quack() {
fmt.Println("meow")
}
func main() {
var c Duck = &Cat{}
c.Quack()
}
// 编译通过,因为指针可以隐式得获取到结构体
type Duck interface {
Quack()
}
type Cat struct{}
func (c *Cat) Quack() {
fmt.Println("meow")
}
func main() {
var c Duck = Cat{}
c.Quack()
}
$ go build interface.go
./interface.go:20:6: cannot use Cat literal (type Cat) as type Duck in assignment:
Cat does not implement Duck (Quack method has pointer receiver)
// 编译失败,因为Quack方法接收的是指针,但传入的是结构体
nil和non-nil
func main() {
type Test struct{}
// 直接对比
var v interface{}
var t *Test
fmt.Println(v == nil)
fmt.Println(t == nil)
// 赋值后再对比
fmt.Printf("%v", Convert(t))
}
func Convert(v interface{}) bool {
return v == nil
}
$ true
true
false
指针的零值是 nil
出现上述现象的原因是 —— 调用 Convert函数时发生了隐式的类型转换,除了向方法传入参数之外,变量的赋值也会触发隐式类型转换。在类型转换时,*Test 类型会转换成 interface{} 类型,转换后的变量不仅包含转换前的变量,还包含变量的类型信息 Test,所以转换后的变量与 nil 不相等。