> For the complete documentation index, see [llms.txt](https://dingyj.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dingyj.gitbook.io/blog/golang/basic/go-yu-yan-xue-xi-bi-ji/go-yu-yan-de-jie-kou.md).

# Go语言接口(Golang - Interface)

## Duck Typing

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

![](/files/-LfhZUISTLIDZCpsUpHB)

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

鸭子的定义：

> 鸭子 英文名称：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的类型检查

## 接口的定义

![](/files/-Lfx-t_60cxCzd1fCRKs)

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 %}

## 接口的值类型

### 接口内部

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

![](/files/-Lg2wlLZqtkgI30dyXKi)

```go
type Retriever struct {
	Contents string
}

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

也可以用实现者的指针

![](/files/-Lg2x2Vbxzuj108qwHb1)

```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)`也需要时指针。不然会报错。

![](/files/-Lg2vBB0nz7kZkyTz_jM)

* 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")
}

```

![](/files/-LgCUcppyFZY6Dr0WzCf)

Go语言接口的好处

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

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

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dingyj.gitbook.io/blog/golang/basic/go-yu-yan-xue-xi-bi-ji/go-yu-yan-de-jie-kou.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
