Go 第5章 面向接口

2021-06-09

一、duck typing的概念

1.1、什么是 duck typing

  • “像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”

  • 描述事物的外部行为而非内部结构

  • 严格说 go 属于结构化类型系统,类似 duck typing

1.2、python 中的 duck typing

def download(retriever):
    return retriever.get("lulublog.cn")
  • 运行时才知道传入的 retriever 有没有 get

  • 需要注释来说明接口

1.3、C++中的 duck typing

template  
string download ( const R& retriever) { 
    return retriever.get( "lulublog.cn") ;
}
  • 编译时才知道传入的 retriever 有没有 get

  • 需要注释来说明接口

1.4、java 中的类似代码


String download(R r) { 
    return r.get ( "lulublog.cn" ) ; 
}
  • 传入的参数必须实现 Retriever 接口

  • 不是duck typing

二、接口的定义和实现

2.1、接口的定义

type Retriever interface { 
    Get(source string) string 

func download(retriever Retriever) string { 
    return retriever.Get("lulublog.cn") 
}

2.2、接口的实现

  • 接口的实现是隐式的

  • 只要实现接口里的方法

2.3、例1

  • main.go

package main

import (
    "fmt"
    "learn/retriever/mock"
)

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("lulublog.cn")
}

func main() {
    var r Retriever
    r = mock.Retriever{"this is a fake lulublog.cn"}
    fmt.Println(download(r))
}
  • mockretriever.go

package mock

type Retriever struct {
    Contents string
}

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

https://file.lulublog.cn/images/3/2022/08/I891j98jq8LsQIIl5k48mjKj988rZs.png

  • 运行结果

go run /mnt/go/src/learn/retriever/main.go

this is a fake lulublog.cn

2.4、例2

  • main.go

package main

import (
    "fmt"
    "learn/retriever/real"
)

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("http://lulublog.cn")
}

func main() {
    var r Retriever
    r = real.Retriever{}
    fmt.Println(download(r))
}
  • retriever.go

package real

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

type Retriever struct {
}

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

https://file.lulublog.cn/images/3/2022/08/By7GW7fJ7wfDND7e0f7eX0rI7nIidY.png

  • 运行结果

https://file.lulublog.cn/images/3/2022/08/IVdBvdzr7odOsW4z7B7z7o4B1SPKq7.png

三、接口的值类型

3.1、接口变量里有什么

  • 实现者的类型

  • 实现者的指针

  • 接口变量自带指针

  • 接口变量同样采用值传递,几乎不需要使用接口的指针

  • 指针接收者实现只能以指针方式使用;值接收者都可

3.2、查看接口变量

3.2.1、Type

  • Type Assertion

  • Type Switch

  • main.go

package main

import (
    "fmt"
    "learn/retriever/mock"
    "learn/retriever/real"
)

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("http://lulublog.cn")
}
//查看接口变量:Type Switch
func inspect(r Retriever) {
    fmt.Printf("%T %v\n", r, r)
    switch v := r.(type) {
        case mock.Retriever:
            fmt.Println("Contents:", v.Contents)
        case *real.Retriever:
            fmt.Println("UserAgent:", v.UserAgent)
    }
}

func main() {
    var r Retriever
    
    r = mock.Retriever{
        Contents: "this is a fake lulublog.cn",
    }
    inspect(r)
    
    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
    }
    inspect(r)
    
    //查看接口变量:Type assertion
    if mockRetriever, ok := r.(mock.Retriever); ok {
        fmt.Println(mockRetriever.Contents)
    }else{
        fmt.Println("not a mock retriever")
    }
    
    realRetriever := r.(*real.Retriever)
    fmt.Println(realRetriever.UserAgent)
    
    // fmt.Println(download(r))
}
  • mockretriever.go

package mock

type Retriever struct {
    Contents string
}

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

package real

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

type Retriever struct {
    UserAgent string
}

func (r *Retriever) Get(url string) string {
    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    
    result, err := httputil.DumpResponse(resp, true)
    
    resp.Body.Close()
    
    if err != nil {
        panic(err)
    }
    
    return string(result)
}
  • 目录结构

https://file.lulublog.cn/images/3/2022/08/zJJ5fPj6Gu4UjYY8j75jpYw0ckFOJ3.png

  • 运行结果

go run /mnt/go/src/learn/retriever/main.go

mock.Retriever {this is a fake lulublog.cn}
Contents: this is a fake lulublog.cn
*real.Retriever &{Mozilla/5.0}
UserAgent: Mozilla/5.0
not a mock retriever
Mozilla/5.0

3.2.2、表示任何类型: interfacef{}

  • queue.go

package queue

type Queue []interface{}

func (q *Queue) Push(v interface{}) {
    *q = append(*q, v)
}

func (q *Queue) Pop() interface{} {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool {
    return len(*q) == 0
}
  • entry.go

package main

import (
    "fmt"
    "learn/queue"
)

func main() {
    q := queue.Queue{1}
    q.Push(2)
    q.Push(3)
    
    fmt.Println(q.Pop())
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())
    
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())
    
    q.Push("abc")
    fmt.Println(q.Pop())
}
  • 目录结构

https://file.lulublog.cn/images/3/2022/08/qLKuHEioyIoLFFHP7OIiF9uf27Y94I.png

  • 运行结果

go run /mnt/go/src/learn/queue/entry/entry.go

1
2
false
3
true
abc

四、接口的组合

  • main.go

package main

import (
    "fmt"
    "learn/retriever/mock"
)

const url = "http://lulublog.cn";

type Retriever interface {
    Get(url string) string
}

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

type RetrieverPost interface {
    Retriever
    Poster
}

func session(r RetrieverPost) string {
    r.Post(url, map[string]string{
        "contents": "another fake lulublog.cn",
    })
    return r.Get(url)
}

func main() {
    mockRetriever := mock.Retriever{
        Contents: "this is a fake lulublog.cn",
    }
    fmt.Println("try a session with mockRetriever")
    fmt.Println(session(&mockRetriever))
}
  • mockretriever.go

package mock

type Retriever struct {
    Contents string
}

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

func (r *Retriever) Post(url string, form map[string]string) string {
    r.Contents = form["contents"]
    return "ok"
}
  • 目录结构

https://file.lulublog.cn/images/3/2022/08/YFYk4oLe4zFnEo00EGTyzp4buBofgP.png

  • 运行结果

go run /mnt/go/src/learn/retriever/main.go

try a session with mockRetriever
another fake lulublog.cn

五、常用系统接口

5.1、Stringer

  • main.go

package main

import (
    "fmt"
    "learn/retriever/mock"
)

type Retriever interface {
}

func main() {
    var r Retriever
    r = mock.Retriever{
        Contents: "this is a fake lulublog.cn",
    }
    fmt.Printf(" > Type:%T Value:%v\n", r, r)
}
  • mockretriever.go

package mock

import "fmt"

type Retriever struct {
    Contents string
}

func (r Retriever) String() string {
return fmt.Sprintf(
"Retriever: {Contents=%s}", r.Contents)
}
  • 目录结构

https://file.lulublog.cn/images/3/2022/08/H277H0FwV2sF9Bk0ffSCj077EE7HWP.png

  • 运行结果

go run /mnt/go/src/learn/retriever/main.go

> Type:mock.Retriever Value:Retriever: {Contents=this is a fake lulublog.cn}
//注释 String() 方法
 > Type:mock.Retriever Value:{this is a fake lulublog.cn}

5.2、Reader/Writer

  • loop.go

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func printFileContents(reader io.Reader) {
    scanner := bufio.NewScanner(reader)
    for scanner.Scan() {
fmt.Println(scanner.Text())
    }
}

func main() {
    fmt.Println("printing a string:")
    s := `abc"d"
    kkkk
    123

    p`
    printFileContents(strings.NewReader(s))
}
  • 运行结果

go run /mnt/go/src/learn/loop/loop.go

printing a string:
abc"d"
kkkk
123

p
阅读 559