• 第五章 接口


    第一天: 接口的定义和实现

    第二天:


    一. go语言是面向接口编程.

      在学习继承的时候说过, go语言只有封装, 没有继承和多态. 那么继承和多态在go中如何实现呢? 通过接口来实现

    1. 接口的定义

    接口定义由两部分组成. 使用者和实现者. 

     接口由使用者定义是什么意思呢?

    比如, 我要下载一个资源. 我定义一个download方法. 这个方法是谁使用谁定义.

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

    给方法传了一个参数r Retriever. 这个参数可以理解为java中的泛型. 我们将它定义成一个接口. 然后谁用他, 谁来实现它. 但他总体的方法是调用接口中的Get

    我们来定义一个接口

    type Retriever interface {
        Get(s string) string
    }

    这就是接口的定义. 接口里面的方法不用写func

    然后我们来调用这个接口试一试

    func main() {
        var r Retriever
        fmt.Println(download(r))
    }

    使用者在调用的时候, 直接调用down就可以了. 这里会返回异常, 因为r是一个空指针

    2. 接口的实现 

    接下来我们来创建一个新的包mock, 然后写一个接口的实现. 

    package mock
    
    type Retriever struct {
        Contents string
    }
    
    func (r Retriever) Get(c string) string{
        return r.Contents
    }

    这就是对Retriever的实现. 这里的Retriever和接口同名. 他们在不同的包中. 然后实现了Get方法. 这样就完成了对Retriever接口的一个实现. 

    很奇怪, 这和java不同, 里面没有出现任何实现关键字, 也没有出现接口关键字. 

    注意: go中接口实现, 只要定义的方法名, 方法参数和返回值和接口定义的一致, 就认为他是对接口的一个实现. 在开发工具中就会自动识别出来

    我们再来写一下实现类

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        fmt.Println(download(r))
    }

    初始化的时候是初始化的接口, 调用的时候, 调的是实现类

    这是一个模拟假的download方法, 返回值如下

    我们再来写一个实现类, 真正的下载方法. 比如我们下载百度的首页面. http://www.baidu.com

    首先创建一个real文件夹, 然后新建一个文件Real

    package real
    
    import (
        "net/http"
        "net/http/httputil"
        "time"
    )
    
    type Real struct {
        url string
        downTime time.Duration
    }
    
    func (r Real) Get(url string) string {
        resp, err := http.Get(url)
        if err != nil {
            panic(err)
        }
    
        result, err := httputil.DumpResponse(resp, true)
    
        if err != nil {
            panic(err)
        }
    
        resp.Body.Close()
    
        return string(result)
    }

    定义了一个方法Get(url string) string .只要一个结构体定义了这个Get方法, 入参和出参和接口定义保持一致, go就认为这个结构体实现了Retriever接口

    我们来看看具体的调用

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        fmt.Println(download(r))
    
        r = real.Real{}
        fmt.Println(download(r))
    }

    这样就调用了接口的第二个实现real.Real

    问题: r变量被两次赋值, 那么他到底是什么类型呢?

    3. 接口如何实现值传递和指针传递

    func (r Real) Get(url string) string 
    使用只传递的方式

    以上方法都是使用值传递, 那么, 如果一个对象很大, 我们不想用值传递, 也可以使用指针传递.

    func (r *Real) Get(url string) string 
    使用指针传递

    那么接收者如何接收呢? 传递过来的是指针. 那么我们接收的时候, 就要接收一个地址

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        fmt.Println(download(r))
    
        r = &real2.Real{"google", 100}
        fmt.Println(download(r))
    }

    否则会报异常. 

    总结: 定义get方法的时候, 前面传的是值(r Real), 那么这个对象就是以值拷贝的方式传递. 如果前面传的值时(r *Real), 那么这个结构体就以地址拷贝的方式传递

    4. 判断接口的类型

    • 方法一: 通过fmt.Printf("%T %v ", r, r)打印的方式
    • 法二: 通过type switch
    • 方法三: 通过type assertion

    方法一: 通过fmt.Printf("%T %v ", r, r)打印的方式

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        fmt.Printf("%T, %v 
    ", r, r)
        fmt.Println(download(r))
    
        r = real2.Real{}
        fmt.Printf("%T, %v", r, r)
        //fmt.Println(download(r))
    }

    结果如下:

    mock.Retriever, {this is a fake www.baidu.com} 
    this is a fake www.baidu.com
    real.Real, { 0}

    方法二: 通过type switch

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        judgeType(r)
        fmt.Println(download(r))
    
        r = &real2.Real{"google", 100}
        judgeType(r)
        //fmt.Println(download(r))
    }
    
    func judgeType(r Retriever) {
        switch r.(type) {
        case mock.Retriever:
            fmt.Println("content:", r.(mock.Retriever).Contents)
        case *real2.Real:
            fmt.Println("ua:", r.(*real2.Real).Ua)
        }
    }

    方法三: 通过type asserttion

    type asserttion就是类型转换. 

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        judgeType(r)
        fmt.Println(download(r))
    
        r = &real2.Real{"google", 100}
        judgeType(r)
    
        realRetriever := r.(*real2.Real)
        fmt.Println(realRetriever)
    
        // 那么转换可能会失败, 失败使用另外一个参数ok来判断
        if rr, ok := r.(mock.Retriever); ok {
            fmt.Println(rr)
        }
        //fmt.Println(download(r))
    }
    r.(*real2.Real)这种类型转换的方式就是type assertion. 他也可以用来判断类型. 但是他可能会转换失败. 我们可以如下用法:
    if rr, ok := r.(mock.Retriever); ok {
            fmt.Println(rr)
     }

    这就是将一个对象进行类型判断的三种方式.

    接口不是简单的引用, 接口的肚子里还有两个东西, 一个是类型, 一个是值

    总结:

    接口变量包含哪些东西呢?

    1. 实现者的类型

    2. 实现者的值或者地址指针, 实现者的指针最终指向的也是实现者的值

      或者 

     第三点: 指针接收者实现只能以指针方式使用; 值接收者都可以. 这句话的含义是, 实现接口方法的时候, 接收者是指针类型, 那么在构建结构体的时候, 只能是指针的方式. 但如果接收者是值传递的方式, 那么结构体可以使用指针接收也可以使用值接收

    例如: 

    package mock
    
    type Retriever struct {
        Contents string
    }
    
    func (r Retriever) Get(c string) string{
        return r.Contents
    }

    这是一个值接收者. 

     使用的时候可以这样使用:

    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        r = &mock.Retriever{"this is a fake www.baidu.com"}
    }

    如果是指针接收者, 那么调用的时候只能使用指针调用

    package real
    
    import (
        "net/http"
        "net/http/httputil"
        "time"
    )
    
    type Real struct {
        Ua string
        TimeOut time.Duration
    }
    
    func (r *Real) Get(url string) string {
        resp, err := http.Get(url)
        if err != nil {
            panic(err)
        }
    
        result, err := httputil.DumpResponse(resp, true)
    
        if err != nil {
            panic(err)
        }
    
        resp.Body.Close()
    
        return string(result)
    }
    func main() {
        var r Retriever    
        r = &real2.Real{"google", 100}    
    }

     5. 任何类型用interface{}表示.

    6. 接口的组合 

    我们上面有了一个下载的方法. 还有一个上传的方法. 怎么写呢? 写法和download是一样的. 上传有上传的url和内容

    // 在定义一个函数post
    func post(p Poster) {
        p.Post("http://www.baidu.com", map[string]string{
            "contents":"提交的内容",
            "name":"aaa",
        })
    }

    然后我们可以在定义一个接口. Poster. 这是反过来思考, 用户需要是用什么, 然后在根据需求写接口

    // 定义了另外一个接口, 提交者Poster
    type Poster interface {
        Post(url string,
            from map[string]string) string
    }

    到这里, 都和download的定义方法一样. 

    我们来给post接口一个实现, 在mock.Retriever结构体中实现Post

    func (r *Retriever) Post(url string,
        from map[string]string) string {
        r.Contents = from["contents"]
        return "ok"
    }

     这里mock.Retriever结构体就实现了两个接口. Get方法实现了Retriever接口, Post实现了Poster接口. 

    那么我们说接口是由使用者来定义的. 那么使用者还有一个需求, 那就是, 我就想能够上传也能够下载. 比如有一个session. 

    已经有了上传接口了, 也有下载接口了, 那么这时候要技能上传又能下载, 怎么办呢?总不能在重写一份吧. 在java里, 我们是怎么做的呢? 再写一个接口,实现上传和下载这两个接口.

    而在go里我们要使用的是接口的组合.

    // 然后又定义了一个session, 他既能缓存Retriever,也能缓存Poster
    type RetrieverPoster interface {
        // 这个时候只需要实现上面两个接口即可
        Retriever
        Poster
    }
    
    
    // 我还想有一个功能, 既能上传也能下载, 需要定义一个新的接口.
    func session(r RetrieverPoster) string{
        r.Post("http://www.baidu.com", map[string]string{
            "contents": "another fake a address",
        })
    
        return r.Get("http://www.baidu.com")
    }

    然后

    func main() {
        retriever := &mock.Retriever{"内容"}
        fmt.Println(session(retriever))
    }

    调用的时候, 我定义的mock.Retriever{}结构体能够直接当做RetrieverPoster类型来使用

    当吧retriever作为参数传给session的时候, 编译器能够知道retriever是结构体mock.Retriever的变量, 而结构体mock.Retriever实现了Get和Post方法

    所以, 可以吧retriever直接作为参数传给session

     7. 组合接口在go sdk中的应用

    例如: ReadWrite相关的接口

     和ReadWrite相关的接口有四个. 这些都是描述他们的能力的 可读可写. 来看看他们是如何实现的

     

     

    以上: 都是采用的接口组合的方式 

    以下是完整的代码

    1. 接口定义

    package main
    
    import (
        "aaa/retriever/mock"
        real2 "aaa/retriever/real"
        "fmt"
    )
    
    // 1. 定义一个接口
    type Retriever interface {
        Get(s string) string
    }
    
    func download(r Retriever) string {
        return r.Get("http://www.baidu.com")
    }
    
    func main() {
        var r Retriever
        r = mock.Retriever{"this is a fake www.baidu.com"}
        fmt.Println(download(r))
    
        r = real2.Real{}
        fmt.Println(download(r))
    }

    2. mock中第一个实现接口的结构体

    package mock
    
    type Retriever struct {
        Contents string
    }
    
    func (r Retriever) Get(c string) string{
        return r.Contents
    }

    3. real中第二个实现接口的结构体

    package real
    
    import (
        "net/http"
        "net/http/httputil"
        "time"
    )
    
    type Real struct {
        url string
        downTime time.Duration
    }
    
    func (r Real) Get(url string) string {
        resp, err := http.Get(url)
        if err != nil {
            panic(err)
        }
    
        result, err := httputil.DumpResponse(resp, true)
    
        if err != nil {
            panic(err)
        }
    
        resp.Body.Close()
    
        return string(result)
    }

       

  • 相关阅读:
    海量图片曝光百度新家“搜索框”大厦
    您玩儿转手机通讯录了吗?
    这是给开发者的弥天大谎还是至理名言?
    互联网创业,不要让经验挡住你前进的道路
    永远不要去请示是否应该整理一下你的代码
    LinkedIn开放软件平台 开发者可集成其技术
    马云建新"淘宝" 借传统媒体补课线下消费群
    乔布斯的五大魔法
    全能 Google 服务提醒软件,GoogSysTray
    Twitter用户偏好新闻 Facebook用户更喜欢科技
  • 原文地址:https://www.cnblogs.com/ITPower/p/12297678.html
Copyright © 2020-2023  润新知