# Go语言接口(Golang - Interface)

## Duck Typing

说Go的接口就必须先了解duck typing的概念。很多其他的语言都支持这个概念。

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-LfhXtFUwb-oqZMtsuuG%2F-LfhZUISTLIDZCpsUpHB%2Fimage.png?alt=media\&token=a1f9636b-6357-47e6-9e25-22fc27d5ff27)

首先上图的这个东西是鸭子吗？

鸭子的定义：

> 鸭子 英文名称：Duck。[脊索动物门](https://baike.baidu.com/item/%E8%84%8A%E7%B4%A2%E5%8A%A8%E7%89%A9%E9%97%A8/3516708)，脊椎[动物](https://baike.baidu.com/item/%E5%8A%A8%E7%89%A9/216062)亚门，鸟纲[雁形目](https://baike.baidu.com/item/%E9%9B%81%E5%BD%A2%E7%9B%AE/2343692)，[鸭科](https://baike.baidu.com/item/%E9%B8%AD%E7%A7%91)[鸭属](https://baike.baidu.com/item/%E9%B8%AD%E5%B1%9E)动物，是由野生[绿头鸭](https://baike.baidu.com/item/%E7%BB%BF%E5%A4%B4%E9%B8%AD/416077)和[斑嘴鸭](https://baike.baidu.com/item/%E6%96%91%E5%98%B4%E9%B8%AD/1025030)驯化而来。是一种常见家禽。鸭是雁形目鸭科鸭亚科水禽的统称。是一种水、陆[两栖动物](https://baike.baidu.com/item/%E4%B8%A4%E6%A0%96%E5%8A%A8%E7%89%A9/305362)。但不能在水中待太久，是卵生动物。中文学名鸭界动物界门脊索动物门亚    门脊椎动物亚门纲鸟纲亚    纲今鸟亚纲目雁形目

按照传统的定义，上面这个图肯定不是鸭子。但是按照duck typing的定义来说，上图这个东西是一只鸭子。因为他长得像鸭子。Duck Typing关注的是描述事物的外部行为而非内部结构。

但是严格来说Duck Typing的定义中要求动态绑定，但是Go是编译就绑定了，所以Go只能说是类似Duck Typing。

### Python和C++中的Duck Typing

```python
def download(retriever);
    return retriever.get("www.google.com")
```

**只有在运行的时候才知道传入的retriever有没有get**，如果没有get方法，download的就会报错，但是如果download和retriever是两组不同的人开发。传入download的retriever必须有get方法这个**信息通常会写在注释里**。

同样C++也有类似的写法，然后C++可以做到**编译的时候知道传入的retriever有没有get**。同时这个信息**还是需要写在注释里。**

### **JAVA中的类似的东西**

```java
<R extends Retriever>
String download(R r){
    return r.get("www.google.com")
}
```

JAVA的这种做法确实很安全，传入的参数必须实现Retriever，也不用写注释了，也不会编译错误了。但是这已经不是Duck Typing，因为你必须实现Retriever接口。导致download不能实现多个接口。（比如read和write）

### Go的Duck Typing

兼备Python的灵活性和JAVA的类型检查

## 接口的定义

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-Lfx-2a-zakq5KO0Skp7%2F-Lfx-t_60cxCzd1fCRKs%2Fimage.png?alt=media\&token=edab2553-0e47-459b-8214-d3c1ba686068)

Go语言中的接口由**使用者**定义

**使用者**

{% code title="User" %}

```go
type Retriever interface {
	Get(url string) string
}
func download(r Retriever) string {
	return r.Get(url)
}
```

{% endcode %}

**实现者**

{% code title="Implementor" %}

```go
type Retriever struct {
	Contents string
}
func (r *Retriever) Get(url string) string {
	return r.Contents
}
```

{% endcode %}

**实际使用的时候**

{% code title="User" %}

```go
func main（） {
    r := mock.Retriever{"test"}
    fmt.print(download(r))
}
```

{% endcode %}

## 接口的值类型

### 接口内部

接口内部可以有实现者的类型和实现者

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-Lg2uCQ3j3pckV1aZKMx%2F-Lg2wlLZqtkgI30dyXKi%2Fimage.png?alt=media\&token=5091d720-895f-4bd5-b035-26b3006f7fb7)

```go
type Retriever struct {
	Contents string
}

func (r Retriever) String() string {
	return fmt.Sprintf(
		"Retriever: {Contents=%s}", r.Contents)
}
```

也可以用实现者的指针

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-Lg2uCQ3j3pckV1aZKMx%2F-Lg2x2Vbxzuj108qwHb1%2Fimage.png?alt=media\&token=11ac18da-2a30-4b48-86e6-136f99807a8f)

```go
func (r *Retriever) String() string {
	return fmt.Sprintf(
		"Retriever: {Contents=%s}", r.Contents)
}
```

### 检测类型

由于Go语言的所有类型都是值类型，所以在实际使用实现了接口的方法的时候`r := mock.Retriever{"test"}` 中的r除了保存了值，也保存了实现者的类型。

查看interace的类型有2中方法：

* Type assertion

```go
if mockRetriever, ok := r.(*mock.Retriever); ok {
		fmt.Println(mockRetriever.Contents)
	} else {
		fmt.Println("r is not a mock retriever")
	}
```

注意这里由于实现者里的方法是传的指针，所以`r.(*mock.Retriever)`也需要时指针。不然会报错。

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-Lg2uCQ3j3pckV1aZKMx%2F-Lg2vBB0nz7kZkyTz_jM%2Fimage.png?alt=media\&token=fcd99d13-c3d4-4880-809d-897483050fb2)

* Switch

```go
switch v := r.(type) {
case *mock.Retriever:
    fmt.Println("Contents:", v.Contents)
case *real.Retriever:
    fmt.Println("UserAgent:", v.UserAgent)
}
```

* 打印类型

```go
fmt.Printf(" > Type:%T Value:%v\n", r, r)
```

### 空接口

`interface{}`代表空接口，可以代表任何类型。

假设有一个队列

```go
type Queue []int
```

如果定义为int的话当然只能往里面加int，但是如果您定义成空接口，就可以传任何值。

```go
type Queue [] interface{}
```

但是在往里面你传值的时候，你可以通过方法限定值得类型

```go
func (q *Queue) Push(v int) {
	*q = append(*q, v)
}
```

这样的话如果`q.push("abc")`的话在编译的时候就能检测的错误。

还有一种方法是在append的时候做转换。

```go
func (q *Queue) Push(v interface{}) {
	*q = append(*q, v.(int))
}
```

但是这样做的话就只能在执行的时候才能检查到错误了所以不推荐。

## 接口的组合

接口内部可以加入其他接口来进行组合。比如很多写入操作的参数都是reader或者writer

```go
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
type ReadWriter interface {
    Reader
    Writer
}
```

## 原生接口的例子

举个原生包的例子，比如写入文件。

使用者`fmt.Fprintf`接受一个io.writer，io.writer是一个接口，里面定义了一个write方法。

再看作为实现者的os.file构造体里面也实现了一个write方法，按照duck typing的规则，file实现了write方法，所以file也是一个writer，所以file也可以传给`fmt.Fprintf`。

```go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Create("result.txt")
    if err != nil {
        log.Fatal("Cannot create file", err)
    }
    defer file.Close()

    fmt.Fprintf(file, "Hello Readers of golangcode.com")
}

```

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-LgCQK2uijXxFy2zEeaU%2F-LgCUcppyFZY6Dr0WzCf%2Fimage.png?alt=media\&token=00047b96-ce5d-4839-b85f-27bb8e774072)

Go语言接口的好处

这样好处是显而易见的，我们拥有非常高的灵活性，同时保证在编译前就能检测到错误。尤其是在团队开发的时候，在其他编程语言当中，都是谁提供服务，谁提供接口。你需要调用我的服务，就必须声明你实现了我的接口。

而这在逻辑上实际是说不通的，服务实现者怎么会确切的知道服务使用者的具体需求呢？当需求发生变化的时候，服务实现者就需要考虑使用者的需求，从而设计接口。而从理论上来说，每一个服务的开发人员都应该专注于自己的服务。

而go语言不同。go语言的接口是**非侵入式接口**，只要调用者本身实现了该接口的全部方法，就默认实现了该接口（事实上也确实是实现了这个接口），而**不需要显示**的声明实现某个接口。这极大的方便了接口的调用，开发人员不必再需要苦想接口的粒度，只需要专注功能函数的实现就可以了。<br>
