# 接口

# 一. 概念

由于 go 是强类型语言,所以需要有接口用于抽象及解耦。

# 二. 示例

下面我们一步步引入接口的概念:
假设我们现在抓取一个页面的内容

package main

import (
  "fmt"
  "io/ioutil"
  "net/http"
)

func main() {
  // 抓取一个页面
  resp, _ := http.Get("https://www.baidu.com")

  // 当页面请求完成时,关闭连接
  defer resp.Body.Close()

  // 读取内容
  bytes, _ := ioutil.ReadAll(resp.Body)

  fmt.Printf("%s\n", bytes)
}

我们来把 main 函数里的内容抽象成一个函数

package main

import (
  "fmt"
  "io/ioutil"
  "net/http"
)

func retrieve(url string) string {
  resp, _ := http.Get(url)
  defer resp.Body.Close()
  bytes, _ := ioutil.ReadAll(resp.Body)
  return string(bytes)
}

func main() {
  fmt.Println(retrieve("https://www.baidu.com"))
}

上述 main 函数还是依赖 retrieve 函数,而这个 retrieve 可以由多个来源,假设我们项目中 infra 中实现了一个 retriever 结构体

package infra

type Retriever struct {}

// 定义结构体方法
func (Retriever) Get(url string) string {
  resp, _ := http.Get(url)
  defer resp.Body.Close()
  bytes, _ := ioutil.ReadAll(resp.Body)
  return string(bytes)
}
package main

import (
  "fmt"
  "go-learning/infra"
)

func getRetriever() infra.Retriever {
  return infra.Retriever{}
}

func main() {
  var retriever infra.Retriever = getRetriever()
  fmt.Println(retriever.Get("https://www.baidu.com"))
}

那现在我们的函数还是依赖 infra 中的 retriever, 假设我们需要改成其他结构体的 Retriever,就会让我们将所有 infra.retriever 改成其他,改动非常大。所以我们需要引入接口,来增强我们代码的健壮性

package main

import (
  "fmt"
  "go-learning/infra"
)

type retriever interface {
  Get(string) string
}

func getRetriever() retriever {
  // 假如我们修改了其他结构体,那么我们无需把所有推断都改一遍了
  return testing.Retriever{}
}

func main() {
  var r retriever = getRetriever()
  fmt.Println(r.Get("https://www.baidu.com"))
}

# 三、接口的定义

像 java 定义接口是由实现者定义,而 go 语言接口的特点是 go 语言接口由使用者定义。

举例:

package main

type Retriever interface {
  Get(url string) string
}

func download(r Retriever) string {
  return r.Get("https://www.baidu.com")
}

func main() {
  var r Retriever = mock.Retriever{"this is a demo"}
  fmt.Println(download(r))
}

上述代码我们定义一个 Retriever 接口, 接口内部定义 Get 方法,但我们并没有实现这个真正的接口,所以下面我们去真实的实现它。

package mock

type Retriever struct {
  Contents string
}

func (r Retriever) Get(url string) string {
  return r.Contents
}

从上述可以看到,当我们真实的实现这个接口的时候,并没有显示的告诉这是一个接口,这里要记住:Go 接口的实现是隐式的,只要实现接口里的方法就可以了。

# 四、struct 和 interface的区别

struct 结构体: 需要声明字段类型和实现函数逻辑, 描述的是一个类

interface 接口: 只用来描述方法(名,参数,返回值即可),不需要实现里面的逻辑

所以struct和interface根本不在一个纬度上,这块要好好理解下。

根据上述例子区分:

// 定义结构体方法
func (Retriever) Get(url string) string {
  resp, _ := http.Get(url)
  defer resp.Body.Close()
  bytes, _ := ioutil.ReadAll(resp.Body)
  return string(bytes)
}

// interface实现的方法
type Retriever interface {
  Get(url string) string
}

# 五、接口的组合

type Retriever interface {
  Get(url string) string
}
 
type Poster interface {
  Post(url string, form map[string]string) string
}

// 组合
type RetrieverPoster interface {
  Retriever,
  Poster
}