初识接口:为什么我们需要接口?

当我们写一个获取网页内容的程序时,我们可能写成这样,满满的C-Style面向过程写法。main()和retrieve()的关联本该不大,但如此写, 耦合 却非常高,main()函数必须要调用retrieve()。
func retrieve(url string) string{  resp,err := http.Get(url)  if err!= nil{    panic(err)  }  defer resp.Body.Close()  bytes,_:= ioutil.ReadAll(resp.Body)  return string(bytes)}func main(){  fmt.Println(retrieve("https://www.douban.com"))}
如果retrieve模块的实现是由另一个团队编写,或者需要替换成测试团队的代码时,高耦合会使之成为一场灾难。我们所需要的不过是一个东西,它能提供一个方法来获取网页信息,翻译到Golang程序中应该是这样:
type retriever interface {  Get(string) string}func getRetriever() retriever {  r := util.Retriever{}  return r}func main() {  r := getRetriever()  fmt.Println(r.Get("http://www.sina.com"))}
如此一来,我们只要实现一个带有 Get( string ) string的方法struct,就可以低耦合地实现原有功能,让main函数不再和唯一的retrieve方法绑定。与java不同,Go中的struct并不需要生命实现了哪个接口,只要实现对应方法,就可是该类型,这是Duck Typing的概念,接口实现如下:
type Retriever struct {}func (Retriever) Get (url string) string{  resp,err := http.Get(url)  if err!= nil{    panic(err)  }  defer resp.Body.Close()  bytes,_:= ioutil.ReadAll(resp.Body)  return string(bytes)}
鸭子类型 可达鸭是鸭子吗?我们如何定义鸭子?

c4797bcc242baca6e86095c44ee283ec.png

如果从传统的面向对象(OOP) 的角度说:鸭子属于鸟纲雁形目、脊椎动物亚门,脊索动物门,它是一层一层继承而来。可达鸭显然不是一只鸭子。但按照duck typing的定义方法,“走路像鸭子,叫声像鸭子,那么就是鸭子”。 duck typing描述事务的外部行为而非内部结构。而duck typing的标准并不唯一,如果从可以吃的角度来看,可达鸭又不能算作鸭子, duck typing是从使用者角度定义的。在Go编程中,我们要和一个东西进行互动,并不关心它是个什么东西,只要它能提供相应的能力就可以。我们先来看看别的语言的duck typing:在python中,我们这样写鸭子类型:
def download(retriever):  return retriever.get("www.douban.com:)
  • 运行时才知道传入的retriever有没有get()

  • 需要注释来说明接口

C++中的duck typing

template <class R>string download(const R& retriever){  return retriever.get("www.douban.com");}  
  • 编译时才知道传入的retriever有没有get

  • 需要注释来说明

Java:

<R extends Retriever>String download(R r){  return r.get("www.douban.com")}
  • 传入的参数必须实现Retriever接口,非常不灵活

  • 不是duck typing

尽管Java在诸多语言中的解决方案中是最好的一个,但是如果需要R实现多个接口,这个就办不到了。

Go语言的duck typing

Go中的duck typing定义:

// 定义接口type Retriever interface {  Get(url string) string}func  Download(r Retriever) string{  return r.Get("http://www.douban.com")}
Go中duck typing的实现:
type Retriever struct {  Contents string}func (r Retriever) Get(url string) string {  return r.Contents}
  • 接口的实现是隐式的

  • 只要实现接口里的方法就可以

接口的值类型

Golang接口的内容并不是一个指针,而是包含了接口的类型和接口的值,使用下面的程序进行测试:

type Retriever interface {  Get(url string) string}type Retriever struct {  UserAgent string  TimeOut time.Duration}r := Retriever{}fmt.Printf("%T %v",r,r)

得到如下结果:

real.Retriever { 0s}

我们还可以使用switch来判断接口类型:

func inspect(r retriever.Retriever){  fmt.Printf("%T %v\n",r,r)  switch r.(type) {  case mock.Retriever:    fmt.Println("This is mock Retriever")  case real.Retriever:    fmt.Println("This is real Retriever")  }}

接口的组合

我们可以通过一下的形式对接口进行组合:

type Retriever interface {  Get(url string) string}type Poster interface {  Post(url string) string}type Obj interface {  Retriever()  Poster()  Option()  }

Go语言标准接口

    go语言中还有诸如Stringer、Reader、Writer等有用的interface{},这样可以和go的基础函数进行交互。