GO语言基础篇(二十)- 接口组合

这是我参与8月更文挑战的第 20 天,活动详情查看: 8月更文挑战

接口的组合

接口的组合就是将一些接口整合在一起,有点像多继承一样。可以直接看下边的例子

type Retriver interface {
    Get(url string) string
}

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

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

func post(poster Poster)  {
    poster.Post("<http://ww.baidu.com>", map[string]string{
        "name": "shulv",
        "age": "twenty",
    })
}
复制代码

上边定义了两个接口,并且分别有这两个接口的调用者,download和post。现在假设有个session函数,它需要一个参数,即是一个Retriver,又是一个Poster。此时就可以用到接口的组合

type RetrieverPoster interface {
    Retriver
    Poster
    //Connect(host string) //当然还可以添加别的方法
}

func session(s RetrieverPoster) string {
    s.Post("<http://www.baidu.com>", map[string]string{
        "Contents": "facked",
    })
    return s.Get("<http://www.baidu.com>")
}
复制代码

上边的代码中定义了一个组合接口RetrieverPoster,它直接包含了Retriver和Poster这两个接口,也就是说他可以直接使用这两个接口中的方法。当然,这个组合接口中还可以增加自己的方法。有没有发现它和结构体的嵌套非常的像(不记得结构体嵌套的,可以点这里

下边为了方便理解,我直接在一个文件中定义结构体,并实现结构体的一些方法

type Mock struct {
    Contains string
}

func (r Mock) Get(url string) string {
    return r.Contains
}

func (r *Mock) Post(url string, form map[string]string) string {
    r.Contains = form["Contents"]
    return "ok"
}

type Real struct {
    UserAgent 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)

    resp.Body.Close()

    if err != nil {
        panic(err)
    }

    return string(result)
}
复制代码

上边定义了两个结构体,分别是Mock和Real,并且实现了对应的方法(可以看到,Mock实现了组合接口RetrieverPoster)。下边在main函数中调用session函数

func main() {
    retriver := Mock{"just a test"}
    fmt.Println(session(&retriever))
}

输出:facked(因为在Post方法中对Mock.Content进行了修改,说明session里边调用了Mock的Post方法)
复制代码

其实在Go语言的库中,有很多接口组合的例子,比如io包里的ReadWriteCloser,它就由多个接口组合而成的一个接口,内容如下:

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}
复制代码

可以看到它是由Reader、Writer和Closer这三个接口组合而成的。当然,你可以进到io这个包里边,里边有很多这样的组合接口

上边就是接口的组合,下边分享一些Go语言中标准的接口

常见系统接口

Stringer

在fmt这个包中,有一个接口叫Stringer,它里边只有一个String函数,所以一个类型只要实现了String函数,就实现了Stringer接口

package fmt

type Stringer interface {
    String() string
}
复制代码

下边定义一个结构体类型,该结构体中实现了String方法

package main

import "fmt"

type Mock struct {
    Contents string
}

func (m Mock) String() string {
    return fmt.Sprintf("Mock: {Contents = %s}", m.Contents)
}

func main() {
    m := Mock{"a test, too"}
    fmt.Println(m.String())
}

输出:Mock: {Contents = a test, too}
复制代码

Reader

在io这个包中有一个Reader接口,它里边只有一个Read接口。实现Read方法的可以是一个文件,可以从文件中读取内容,放入到byte(当然不止有文件,还有网络、slice等等,都可以)

package io

type Reader interface {
    Read(p []byte) (n int, err error)
}
复制代码

可以看一下os.Open方法的内部实现,它的返回值是一个File的结构,然后进入到File中可以看到它是一个结构体类型,并且可以发现这个结构体中内嵌了一个file的结构体,而这个file实际上是 *File的真实表示。而 *File又实现了Read方法,所以它实际上就实现了Reader接口

//file.go
package os

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

//types.go
package os

type File struct {
    *file // os specific
}
....

// file is the real representation of *File.
type file struct {
    pfd         poll.FD
    name        string
    dirinfo     *dirInfo // nil unless directory being read
    nonblock    bool     // whether we set nonblocking mode
    stdoutOrErr bool     // whether this is stdout or stderr
    appendMode  bool     // whether file is opened for appending
}

而*File实现了Read接口,内容如下:
//file.go
package os

func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)
}
复制代码

在*File中可以发现,它不会说我实现了哪些接口。实现了哪些接口是用的人来说的,它只需要实现某些方法就行了

所以我们在获取文件中的内容的时候调用的bufio.NewScanner()方法,它需要的参数是一个io.Reader类型(所以Open返回的File类型,是可以直接传递给NewScanner,因为它实现了Reader接口)

filename := "a.txt"
file, err := os.Open(filename)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    fmt.Println(scanner.Text())//逐行读取文件中的内容
}

//下边是NewScanner内部实现
func NewScanner(r io.Reader) *Scanner {
    return &Scanner{
        r:            r,
        split:        ScanLines,
        maxTokenSize: MaxScanTokenSize,
    }
}
复制代码

Writer

跟Reader接口一样,它也在io包中,接口中只有一个Write方法。实现Write方法的可以是一个文件,将提供的byte数据写入到文件中

package io

type Writer interface {
    Write(p []byte) (n int, err error)
}
复制代码

在fmt包中有一个Fprintf函数(fmt中各种输出函数的使用及作用,可以点这里),Fprintf的作用是返回一个格式化后的字符串。它的第一个参数是一个io.Writer类型,所以任何实现了这个Writer接口的,都能够传给它来用

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}
复制代码

示例:读取文件内容

以读取文件内容为例,通常的做法如下

package main

import (
    "bufio"
    "fmt"
    "os"
)

func printFile(filename string)  {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())//逐行读取文件中的内容
    }
}

func main() {
    printFile("a.txt")
}
复制代码

这样的做法缺点就是,printFile只能接收一个文件名字符串,只能读取文件中的内容。下边对他进行优化,让他不仅能够打印文件内容,并且还能够通过像打印文件一样打印字符串

package main

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

func printFile(filename string)  {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }

    printFileContents(file)
}

func printFileContents(read io.Reader)  { 
    scanner := bufio.NewScanner(read)
    for scanner.Scan() {
        fmt.Println(scanner.Text())//逐行读取文件中的内容
    }
}

func main() {
    printFile("a.txt")
    s := `uiieo
    "dgsjg"
    s
d
3
	`
    printFileContents(strings.NewReader(s))
}
复制代码

可以看到将实际的打印内容工作交由printFileContents方法来做,它接收一个Reader类型的参数,所以可以利用它来打印文件内容,也能将字符串类型以文件的方式打印,这样它就变得更加通用

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享