Interface

ace_kylin · 2018-08-27 15:06:16 · 2141 次点击 · 预计阅读时间 6 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2018-08-27 15:06:16 的文章,其中的信息可能已经有所发展或是发生改变。

接口interface

golang是面向接口的编程语言,这是golang的语言特点之一。golang传统意义上的继承,多态等,它只支持封装。golang中的接口并不是传统(c/c++,java等)意义上的接口,而是传统面向对象中继承和多态在golang中是通过接口来完成的。在golang的interface出现之前,接口主要作为不同组建之间的契约存在的,对契约的实现是强制的,必须明确声明实现了这个接口。但是golang完全不同。多态是指代码可以根据类型的具体实现采用不同行为的能力,接口是一个或多个方法签名的集合,只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口,这称为StructuralTyping,接口只有方法声明,没有实现,没有数据字段接口可以匿名嵌入其它接口,或嵌入到结构中,将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针,只有当接口存储的类型和对象都为nil时,接口才等于nil,接口调用不会做receiver的自动转换,接口同样支持匿名字段方法,接口也可实现类似OOP中的多态,空接口可以作为任何类型数据的容器

eg:

type Traversal interface{
    Traverse()
}
func main(){
    traversal := getTraversal()
    traversal.Traverse()
}

通过getTraversal()方法封装得到了一个traversal,这个traversal实现了Traversal这个interface

duck typing

许多语言支持duck typing,包括c++,python,java无法实现duck typing

duck typing的概念:像鸭子走路,像鸭子叫,长的像鸭子,那么它就是一只鸭子

duck typing的用途:描述事物的外部行为而非内部结构

在golang编程中就利用duck typing的方式进行,gopher不用在意调用方法的“类型”是一个什么,当gopher需要调用到某个方法时能提供这个方法即可,然后和这些方法进行互动

长得像鸭子就是鸭子,也就是说实现了interface就是实现了interface中定义的那个方法,那么实现该方法的struct就不用赋值给interface就拥有了这个能力。也就是说实现了这个struct的实例可以被fmt.Println调用。但是在定义的时候其实是interface实例被调用。只不过这个struct实现了这个接口,所以它也拥有了这个能力。

严格说来go属于结构化类型系统,类似duck typing,因为在定义duck typing时强调了“动态绑定”,但是golang是编译时就绑定了,从这个角度看,golang就不是duck typing

golang接口的定义

graph LR
使用者-->实现者

golang中,一个类只需要实现接口要求的所有函数,我们就说这个类实现了这个接口

golang的非侵入式接口,看似做了很小的语法调整,实则影响很是深远:

  1. go标准库,再也不需要绘制类库继承树图;
  2. 实现类的时候,只需要关心自己应该实现哪些方法,不必纠结接口要拆得多细才合理,接口由使用方法按需定义,而不用事前规划;
  3. 不用为了实现一个接口而导入一个包,因为多引用一个外部包,就意味着更多的耦合,接口使用方按自身需求定义,使用方无需关心是否有其他模块定义过类似的接口。

代码示例,自主实现一个下载https://studygolang.com/网页的代码(其中含有多态的概念):

main()

package main

import (
    "fmt"
    "interfaceType/mock"
    real2 "interfaceType/real"
)

type Link interface {
    Get(url string) string
}

func download(l Link)string  {
    return l.Get("https://studygolang.com/")
}
func main() {
    var l,m Link
    l = mock.Retriever{"This is a fack link"}
    fmt.Println(download(l))
    m = new(mock.Retriever)
    fmt.Println(download(m))

    var r Link
    r = real2.Retriever{}
    fmt.Println(download(r))
}

mockRetriever

package mock

type Retriever struct {
    Contents string
}

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

realRetriever

package real

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

type Retriever struct {
    UserAgent string
    TimeOut   time.Duration
}

func (r Retriever) Get(url string) string {
    response, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    result, err := httputil.DumpResponse(response, true)
    if err != nil {
        panic(err)
    }
    defer response.Body.Close()
    return string(result)

}

利用标准库实现下载一个下载https://studygolang.com/网页的代码:

package main

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

// main is the entry point for the application.
func main() {
    // Get a response from the web server.
    r, err := http.Get("https://studygolang.com/")
    if err != nil {
        fmt.Println(err)
        return
    }
    // Copies from the Body to Stdout.
    io.Copy(os.Stdout, r.Body)
    if err := r.Body.Close(); err != nil {
        fmt.Println(err)
    }
}

观察以上代码,不难发现,使用标准库实现网页的download更容易一些当然,我们这里要研究的东西是golang的接口,download的实现是次要的。

r, err := http.Get("https://studygolang.com/")

利用http.Get()函数得到一个http.Response类型的指针,观察一下http.Get()函数源码:

func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

在观察一下http.Response:

type Response struct {
    Status     string 
    StatusCode int   
    Proto      string 
    ProtoMajor int   
    ProtoMinor int   
    Header Header
    Body io.ReadCloser
    ContentLength int64
    TransferEncoding []string
    Close bool
    Uncompressed bool
    Trailer Header
    Request *Request
    TLS *tls.ConnectionState
}

原来http.Response是一个结构,不难发现里面的Body是一个io.ReadCloser类型的,让我们再去看看io.ReadCloser的源码:

type ReadCloser interface {
    Reader
    Closer
}

原来Body是一个接口组合,实现了Reader和Closer方法

接下来继续看

io.Copy(os.Stdout, r.Body)

io.Copy()函数的源码:

func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil)
}

第一个参数是一个Writer,第二个参数是一个Reader,当然,我们的Body实现了Reader这个接口,所以可以直接作为参数传入。

接口赋值

golang接口的赋值有以下两种:

  1. 将对象实例赋值给接口:要求该对象实例实现了接口要求的所有方法
  2. 将一个接口赋值给另外一个接口:

将实例化对象赋值给接口

package main

import "fmt"

type myInt int
type LessAdd interface {
    Less(b myInt) bool
    Add(b myInt)
}

func main() {
    var a myInt = 2
    var b LessAdd = &a
    b.Add(a)
    fmt.Println(b.Less(0))
    fmt.Printf("%d\n",a)
}
func (a myInt) Less(b myInt) bool {
    return a < b
}
func (a *myInt) Add(b myInt) {
    *a += b
}

有一个有趣的问题:a赋值给接口LessAdd时候,用的是地址的引用,而不是值的引用

    var a myInt = 2
    var b LessAdd = &a

接口LessAdd中包含了一个指针的方法func (a myInt) Add(b myInt),在调用非指针方法func (a myInt) Less(b myInt) bool时候,golang会自动生成一个函数func(a myInt)Less(b myInt)bool,这就解决了指针传入的问题


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2141 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传