Golang学习笔记十一

面向接口-接口的定义与实现

Posted by CDz on February 1, 2019

上一篇我们介绍了duck typing概念,主要讲述GO语言中,如何解决duck typing中编译器不提示错误,和没有duck typing的Java灵活性不够问题.

如何定义一个接口

GO中定义接口可以使用关键字interface

type Retriever interface { //使用interface关键字
    Get(url string)string //接口的方法
}

接口的实现

然后定义一个函数,参数为我们定义的Retriever

func download(r Retriever) string {//参数为 Retriever 接口
    return r.Get("http://www.imooc.com")
}

所有实现了Retriever接口的结构体都可以传入函数.

定义一个结构体

package mook

type Retriever struct {
    Contents string
}

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

可以看到次结构体,在形式上,与我们定义的Retriever interface没有任何联系.只是里面有一个Get方法,参数与返回值与接口中定义的方法一样.

测试:

func main() {
    var r Retriever = mook.Retriever{"this is a fake imook.com"}
    s := download(r)
    fmt.Println(s)
}

看到是可以打印的,即在接口的实现上,GO语言是有实现者来控制的.

这样做的好处?我们再来实验一下把mook.Retriever中Get方法删除.可以看到调用download函数时,会报错.这是在编译器时就报错,对于编程人员非常的友好.不至于在运行时报错.具有了Java接口定义的特性,但是在实现上又与接口无关,也就是说,接口的变化是不会强制影响其他实现者的.只有在实现者使用其改变的接口地方需要修改.

实现一个HTTP请求的Retriever

package real

import (
    "net/http"
    "net/http/httputil"
    "time"
)

type Retriever struct {

    UserAgent string
    TimeOut time.Duration

}

func (r Retriever) Get(url string) string {

    resp, err := http.Get(url)

    if err!=nil {
        panic(err) //这里我们遇到错误就panic其实是不对的,
                    //后面会讲到正确处理错误的方式
    }

    result, err := httputil.DumpResponse(resp, true)

    resp.Body.Close()
    if err!=nil {

        panic(err)

    }
    return string(result)
}

接口的类型判断

接口类型如何去判断?在Java中是使用instanceof来判断接口是否是指定类型.在GO语言中有两种方式.

  • type switch
  • type assertion
//type switch
    switch v:=r.(type) {
    case mook.Retriever:
        fmt.Println("Contents:",v.Contents)
    case real2.Retriever:
        fmt.Println("UserAgent:",v.UserAgent)

    }
    //type assertion
    if retriever,ok := r.(real2.Retriever);ok{
        fmt.Println(retriever.UserAgent)
    }else if retriever,ok := r.(mook.Retriever) ;ok{
        fmt.Println(retriever.Contents)
    }else {
        fmt.Println("类型错误")
    }

一般使用type assertion来判断接口的类型.这里我们发现其实这是一个类型转换,而且GO语言中,类型转换还会给我们返回两个值,第一个是转换的类型,第二个是否转换成功.

接口里面是什么东西?

这一小节我们来研究一下,接口里面是什么东西.

func main() {
    var r Retriever = mook.Retriever{"this is a fake imook.com"}
    fmt.Printf("%T %v\n",r,r)
    r= real2.Retriever{
        UserAgent:"Mozilla/5.0",
        TimeOut:time.Minute,
    }
    fmt.Printf("%T %v\n",r,r)
}

打印:

mook.Retriever {this is a fake imook.com}
real.Retriever {Mozilla/5.0 1m0s}

可以看出interface 其内部有一个 interface 类型和值 (值也可以是指针)

所以,其实interface是一个数据结构,其中包含了接口的类型与实现者的值.


作者所有的学习源码在 go学习源码github地址,如果觉得有用的话帮小智贡献一个star😁