本节介绍使用接口时遇到的一些常见问题以及它的设计与实现,包括接口的类型转换、类型断言以及动态派发机制

概述
在计算机科学中,接口是计算机系统中多个组件共享的边界,不同的组件能够在边界上交换信息1。如下图所示,接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口。
image.png
关键字:组件共享的边界、中间层

  • 解耦

例如:
有上下游关系的解耦:可移植操作系统接口(操作系统来实现),让计算机软件可移植

  • 隐藏实现,减少关注点

    代码必须能够被人阅读,只是机器恰好可以执行

例如:
使用SQL不需要关注底层数据库的具体实现

隐式接口

Golang中的接口的实现都是隐式的。例如:

  1. type error interface {
  2. Error() string
  3. }
  4. type RPCError struct {
  5. Code int64
  6. Message string
  7. }
  8. func (e *RPCError) Error() string {
  9. return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
  10. }

只需要实现了Error() string 方法,就实现了error接口
我们使用上述 RPCError 结构体时并不关心它实现了哪些接口,Go 语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查。

类型

接口也是 Go 语言中的一种类型,它能够出现在变量的定义、函数的入参和返回值中并对它们进行约束,不过 Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的 interface{}:
image.png

  • runtime.iface
  • runtime.eface : 不包含任何方法的接口 interface{}

注意:
与 C 语言中的 void 不同,interface{} 类型*不是任意类型。如果我们将类型转换成了 interface{} 类型,变量在运行期间的类型也会发生变化,获取变量类型时会得到 interface{}

  1. package main
  2. func main() {
  3. type Test struct{}
  4. v := Test{}
  5. Print(v)
  6. }
  7. func Print(v interface{}) {
  8. println(v)
  9. }

上述函数不接受任意类型的参数,只接受 interface{} 类型的值,在调用 Print 函数时会对参数 v 进行类型转换,将原来的 Test 类型转换成 interface{} 类型,本节会在后面介绍类型转换的实现原理。

当接口的实现者为结构体,那么结构体就会在方法调用时发生复制
当接口的实现者为指针,那么指针就会在方法调用时进行复制
image.png

  1. type Cat struct{}
  2. func (c Cat) Quack() {
  3. fmt.Println("meow")
  4. }
  5. func main() {
  6. var c Duck = &Cat{}
  7. c.Quack()
  8. }
  9. // 编译通过,因为指针可以隐式得获取到结构体
  1. type Duck interface {
  2. Quack()
  3. }
  4. type Cat struct{}
  5. func (c *Cat) Quack() {
  6. fmt.Println("meow")
  7. }
  8. func main() {
  9. var c Duck = Cat{}
  10. c.Quack()
  11. }
  12. $ go build interface.go
  13. ./interface.go:20:6: cannot use Cat literal (type Cat) as type Duck in assignment:
  14. Cat does not implement Duck (Quack method has pointer receiver)
  15. // 编译失败,因为Quack方法接收的是指针,但传入的是结构体

nil和non-nil

  1. func main() {
  2. type Test struct{}
  3. // 直接对比
  4. var v interface{}
  5. var t *Test
  6. fmt.Println(v == nil)
  7. fmt.Println(t == nil)
  8. // 赋值后再对比
  9. fmt.Printf("%v", Convert(t))
  10. }
  11. func Convert(v interface{}) bool {
  12. return v == nil
  13. }
  14. $ true
  15. true
  16. false

指针的零值是 nil
出现上述现象的原因是 —— 调用 Convert函数时发生了隐式的类型转换,除了向方法传入参数之外,变量的赋值也会触发隐式类型转换。在类型转换时,*Test 类型会转换成 interface{} 类型,转换后的变量不仅包含转换前的变量,还包含变量的类型信息 Test,所以转换后的变量与 nil 不相等。