# 2.接口

Go 语言中的接口是一组方法的签名。接口的本质是引入一个新的中间层，调用方可以通过接口与具体实现分离，解除上下游的耦合，上层的模块不再需要依赖下层的具体模块，只需要依赖一个约定好的接口。

实现上，interface 实际结构为动态类型 + 动态值，根据是否包含方法，又分为 `iface` 和 `eface` 两种，`eface` 多带一个函数地址列表

这种面向接口的编程方式有着非常强大的生命力，无论是在框架还是操作系统中我们都能够找到接口的身影。可移植操作系统接口（Portable Operating System Interface，POSIX）就是一个典型的例子，它定义了应用程序接口和命令行等标准，为计算机软件带来了可移植性 — 只要操作系统实现了 POSIX，计算机软件就可以直接在不同操作系统上运行。

除了解耦有依赖关系的上下游，接口还能够帮助我们隐藏底层实现，减少关注点。人能够同时处理的信息非常有限，定义良好的接口能够隔离底层的实现，让我们将重点放在当前的代码片段中。SQL 就是接口的一个例子，当我们使用 SQL 语句查询数据时，其实不需要关心底层数据库的具体实现，我们只在乎 SQL 返回的结果是否符合预期。

#### 隐式接口

很多面向对象语言都有接口这一概念，例如 Java 和 C#。这里简单介绍一下 Java 中的接口：

```Java
// 接口定义
public interface MyInterface {
    public void sayHello();
}

// 实现接口的类
public class MyInterfaceImpl implements MyInterface {
    public void sayHello() {
        System.out.println("Hello");
    }
}
```

Java 中的类必须通过上述方式显式地声明实现的接口，但是在 Go 语言中实现接口就不需要使用类似的方式。一个常见的 Go 语言接口是这样的：

```go
// 接口定义
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)
}
```

Go 语言实现接口的方式与 Java 完全不同：

* 在 Java 中：实现接口需要显式地声明接口 (`implements` 关键字) 并实现所有方法；
* 在 Go 中：实现接口的所有方法就隐式地实现了接口；

我们使用上述 `RPCError` 结构体时并不关心它实现了哪些接口，Go 语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查，这里举几个例子来演示发生接口类型检查的时机：

```go
func main() {
    var rpcErr error = NewRPCError(400, "unknown err") // typecheck1
    err := AsErr(rpcErr) // typecheck2
    println(err)
}

func NewRPCError(code int64, msg string) error {
    return &RPCError{ // typecheck3
        Code:    code,
        Message: msg,
    }
}

func AsErr(err error) error {
    return err
}
```

Go 语言在[编译期间](https://draveness.me/golang/docs/part1-prerequisite/ch02-compile/golang-compile-intro/)对代码进行类型检查，上述代码总共触发了三次类型检查：

1. 将 `*RPCError` 类型的变量赋值给 `error` 类型的变量 `rpcErr`；
2. 将 `*RPCError` 类型的变量 `rpcErr` 传递给签名中参数类型为 `error` 的 `AsErr` 函数；
3. 将 `*RPCError` 类型的变量从函数签名的返回值类型为 `error` 的 `NewRPCError` 函数中返回；

从类型检查的过程来看，编译器仅在需要时才检查类型，类型实现接口时只需要实现接口中的全部方法，不需要像 Java 等编程语言中一样显式声明。

#### 数据结构

Go 语言中有两种略微不同的接口，一种是带方法的，Go语言中使用 `runtime.iface` 结构；一种是不带方法的，使用 `runtime.eface` 结构。两者都为动态类型 + 动态值，`eface` 还会多一个方法列表

#### eface (empty face)

```go
type eface struct { // 16 字节
    _type *_type
    data  unsafe.Pointer
}

// 类型的运行时表示
// 包含了很多类型的元信息，例如：类型的大小、哈希、对齐以及种类等
type _type struct {
    size       uintptr // 类型占用的内存空间，为内存空间的分配提供信息
	  ptrdata    uintptr 
  	hash       uint32  // 快速确定类型是否相等
	  tflag      tflag
  	align      uint8
	  fieldAlign uint8
  	kind       uint8
	  equal      func(unsafe.Pointer, unsafe.Pointer) bool // 判断当前类型的多个对象是否相等，该字段是为了减少 Go 语言二进制包大小从 typeAlg 结构体中迁移过来的
  	gcdata     *byte
	  str        nameOff
  	ptrToThis  typeOff
}
```

#### iface

```go
// runtime/runtime2.go
type iface struct { // 16 字节
    tab  *itab
    data unsafe.Pointer
}

type itab struct { // 32 字节
    inter *interfacetype // 接口定义的类型信息
    _type *_type         // 接口实际指向值的类型信息
    hash  uint32         // 用于快速判断目标类型和具体类型 runtime._type 是否一致
    _     [4]byte
    fun   [1]uintptr     // 接口方法实现列表，即函数地址列表，按字典序排序
}

// runtime/type.go
type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod      // 接口方法声明列表，按字典序排序
}
// 接口的方法声明 
type imethod struct {
    name nameOff           // 方法名
    ityp typeOff           // 描述方法参数返回值等细节
}
```

### 类型转换

以如下代码为例：

```go
var c Duck = &Cat{Name: "draven"}
c.Quack()
```

可以拆解为三步：

1. 结构体 `Cat` 的初始化 (\&Cat{Name: "draven"}\`)
2. 赋值触发的类型转换过程
   1. 先构建 `itab` 对象，其内容包含 `Cat` 的类型、函数方法
   2. 再调用 `runtime.convT2I` 方法构建 `iface` 对象，其 `itab` 字段为上面一步的结果，`data` 字段为指向 `Cat` 的指针
3. 调用接口的方法 `Quack()`
   1. 其 `itab` 结构内的 `fun` 字段存储了 `Cat` 的函数索引

### 类型断言

```go
func main() {
    var c Duck = &Cat{Name: "draven"}
    switch c.(type) {
    case *Cat:
        cat := c.(*Cat)
        cat.Quack()
	}
}
```

```go
00058 CMPL  go.itab.*"".Cat,"".Duck+16(SB), $593696792
                                        ;; if (c.tab.hash != 593696792) {
00068 JEQ   80                          ;;
00070 MOVQ  24(SP), BP                  ;;      BP = SP+24
00075 ADDQ  $32, SP                     ;;      SP += 32
00079 RET                               ;;      return
                                        ;; } else {
00080 LEAQ  ""..autotmp_4+8(SP), AX     ;;      AX = &Cat{Name: "draven"}
00085 MOVQ  AX, (SP)                    ;;      SP = AX
00089 CALL  "".(*Cat).Quack(SB)         ;;      SP.Quack()
00094 JMP   70                          ;;      ...
                                        ;;      BP = SP+24
                                        ;;      SP += 32
                                        ;;      return
                                        ;; }
```

switch语句生成的汇编指令会将目标类型的 `hash` 与接口变量中的 `itab.hash` 进行比较：

* 如果两者相等意味着变量的具体类型是 `Cat`，我们会跳转到 0080 所在的分支完成类型转换。
  1. 获取 SP+8 存储的 `Cat` 结构体指针；
  2. 将结构体指针拷贝到栈顶；
  3. 调用 `Quack` 方法；
  4. 恢复函数的栈并返回；
* 如果接口中存在的具体类型不是 `Cat`，就会直接恢复栈指针并返回到调用方

### 动态派发

动态派发（Dynamic dispatch）是在运行期间选择具体多态操作（方法或者函数）执行的过程，它是面向对象语言中的常见特性6。Go 语言虽然不是严格意义上的面向对象语言，但是接口的引入为它带来了动态派发这一特性，调用接口类型的方法时，如果编译期间不能确认接口的类型，Go 语言会在运行期间决定具体调用该方法的哪个实现。

在如下所示的代码中，`main` 函数调用了两次 `Quack` 方法：

1. 第一次以 `Duck` 接口类型的身份调用，调用时需要经过【运行时】的动态派发；
2. 第二次以 `*Cat` 具体类型的身份调用，【编译期】就会确定调用的函数：

```go
func main() {
	var c Duck = &Cat{Name: "draven"}
	c.Quack()
	c.(*Cat).Quack()
}
```

#### reflect

`reflect` 包基本是依赖 `interface` 来实现的。里面定义了一个接口和一个结构体，即 `reflect.Type` 和 `reflect.Value`，提供很多函数来获取存储在接口里的类型信息。

`reflect.Type` 主要提供关于类型相关的信息，所以它和 `_type` 关联比较紧密；`reflect.Value` 则结合 `_type` 和 `data` 两者，因此程序员可以获取甚至改变类型的值。

`reflect` 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体：

```
func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value
```

调用这两个函数时，实参会先被值拷贝，转为 `interface{}` 类型。这样，实参的类型信息、方法集、值信息都存储到 `interface{}` 变量里了。

**参考**

[draveness - 4.2 接口](https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/)
