每个Go开发者都应该知道的重要接口

原文:https://spino.tech/blog/important-go-interfaces/#golang
在Go语言中,接口是一个非常重要的概念。它们提供了一种简单而有效的方式来表达类型间的共同行为。对于我们需要某种多态性的典型情况,它们给我们提供了易于理解的解决方案。这就是Golang开发人员一直在使用接口的原因。

一些接口比其他接口更特别。Go标准库中定义了最基本的。它们被使用并可以在每个Go项目中找到。每个Golang开发者都应该知道这些最重要的接口。通过这种方式,人们可以通过查看方法签名来轻松确定给定类型实现哪个知名接口。它还使我们能够掌握在调用所有标准和使用的接口的实现方法时可以期待的行为。标准接口还向我们展示了如何设计良好的接口(其中一个是惯用的Go代码)。

在这篇博客文章中,我将介绍一些最重要和有用的知识接口和语义。

在此之后,一篇稍长的介绍让我们看一个实际的列表:

内置界面 - error
错误是一个内置的接口,它描述可被视为错误值的类型。错误接口定义为:

type error interface {
    Error() string
}

正如你所看到的,这是一个非常简单的接口。用于描述某种错误的Go中的每个类型都必须实现一个方法 - Error()。它的目的是提供有关给定错误的精确信息,包括详细上下文。

大多数时候你不需要自己创建这个接口的实现。你可以在包中找到helper方法errors。例如,要创建新的错误值,可以这样写:

myError := errors.New("Something goes wrong")

如果你想包装另一个错误的错误,并提供给它,你可以使用功能更多的上下文Errorf从fmt包

if err != nil {
    return fmt.Errorf("Error occured: %v", err)
}

如果您正在寻找更强大的解决方案,可以帮助您有效处理Go中的错误,则可以使用 https://github.com/pkg/errors/软件包。通过使用Wrap该包中的函数,您可以创建含有功能堆栈跟踪的有意义的错误消息。这个解决方案比使用更好fmt.Errorf

io.Reader
该接口对于各种类型的文件系统和网络通信任务非常重要。它是这样定义的:

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

其定义包含一种方法Read()。该方法将从len(p)其定义的源中读取字节。字节将被保存在切片中p []byte。如果n出现错误,此方法将返回读取的字节数()和错误(err)。

例如,如果您打开一个文件然后调用该Read()方法,您将从该文件中读取字节:

file, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := make([]byte, 10)

// Try to read 10 or less bytes in case of EOF
n, err := file.Read(content)

此方法也具有相同的网络连接语义,您可以从中读取数据,就像从文件一样。

一个ioutil包定义了一种方法ReadAll,当你想一次读取整个文件时[ doc ](或者直到EOF从任何实现io.Reader接口的源代码读取)都会有帮助。

...
file, err := os.Open("file.txt")
...


// ReadAll argument is io.Reader.
// It turns out that struct os.File is implementing this interface,
// as we saw before, so we can use it here.
b, err := ioutil.ReadAll(file),
if err != nil {
    // handle error
}

// b slice contains all bytes of file

通过使用io.Reader接口,我们可以将其实现之一封装在另一个实现中。这给了我们一种实现诸如以下方面的习惯方式:

从压缩文件中读取
从压缩网络中读取TCP流
从加密的网络连接读取数据
以下是从压缩文件中读取的示例:

import "compress/gzip"

...

file, err := os.Open("archive.gz")
...

// Wrap os.File with gzip.Reader
// We can do this beacause gzip.NewReader expects io.Reader implementation
// as argument and os.File is implementing it
decompressReader, err := gzip.NewReader(file)

c := make([]byte, 10)

// Read 10 decompressed bytes
n, err := decompressReader.Read(c)

if err != nil {
    // handle errors
}

a := c[0] // use decompressed data

io.Writer
这个接口与io.Reader非常相似。我们用它来写入字节到各个目的地。其定义也非常简单:

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

这个接口有一个方法 - - Write()接受一个参数 - bytes p([]byte)的片段。然后它将这部分字节写入某个定义了该方法的输出。最后,它返回n- 已写入输出的字节数,以及写入error期间是否有错误。io.Writer我使用简单的例子包括将文件写入文件或网络连接。

此示例显示如何将文本写入'Test\n'文件:

...
file, err := os.Create("file.txt")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := []byte("Test\n")

n, err := file.Write(content)
if err != nil {
    log.Printf("Error while writeing to file: %v", err)
}
...

类似于io.Reader,io.Writer接口可以彼此缠绕。这给了我们与以下相反的结果io.Reader,例如:

将压缩字节写入文件
将压缩字节写入网络连接
这个例子展示了我们如何将压缩字节写入文件:

import "compress/gzip"

...

file, err := os.Create("file.txt.gz")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := []byte("Test\n")

// Wrap os.File with gzip.Writer
compressedWriter := gzip.NewWriter(file)

// Write compressed bytes
n, err := compressedWriter.Write(content)
if err != nil {
    log.Printf("Error while writeing to file: %v", err)
}
...

io.ReadWriter
这是第一个呈现的界面,它是Golang界面组成的例子。这个接口是这样定义的:

type ReadWriter interface {
    Reader
    Writer
}

正如你所看到的,这个接口由两个其他接口组成:

io.Reader
io.Writer
它表示为可以读取和写入的内容定义的方法集。例如:

os.File
bytes.Buffer
通过定义io.Reader和io.Writer小一个方法的接口,我们现在可以编写他们进入一个新的。

io.Closer
该接口是为使用后需要关闭的对象定义的。立即想到的一个例子是os.File。这个接口定义非常简单:

type Closer interface {
    Close() error
}

在这个界面中,我们只有一种方法 - Close。它用于报告给定资源的使用完成情况。当我们使用缓冲的io(package bufio)写入文件时,这种方法也很重要,我们需要确保所有字节都保存到文件中。

方法Close与defer关键字一起用于很多情况:

func foo() {
    f, err := os.Open("file.txt")
    if err != nil {
        //error handling
    }

    // Call Close() when we will be returning form current function
    defer func() {
        err := f.Close()
        if err != nil {
            // error when closing file
        }
    }()

    ...

}

io.WriteCloser
这是接口的下一个例子,将两个简单的接口组合成一个更大的接口。这个接口是这样定义的:

type WriteCloser interface {
    Writer
    Closer
}

它结合了io.Writer和的功能io.Closer。
io.ReadWriteCloser
该界面将三个简单界面组合在一起

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

fmt.Stringer
这个接口功能类似于str()Python和toString()Java中的方法。它用于定义给定对象的文本表示。这个接口有一个方法String():

type Stringer interface {
    String() string
}

当对象被传递给该方法被隐含地调用fmt.Printf 功能和动词是有效的字符串(%s,%q,%v,%x,%X)。请注意,如果一个对象实现了两者String()和Error()方法,那么该Error()方法将被使用fmt.Printf。
fmt.GoStringer
此接口可用于以fmt.Printf格式string(%#v)更改Go语法表示动词的行为。默认情况下,这个动词将产生一个有效的Go源代码对象的表示。如果你想改变这个,那么你需要实现这个接口:

type GoStringer interface {
    GoString() string
}

net.Conn
这个界面比以前更复杂。它有更多的方法,它们被设计用于处理网络数据流。

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

这net.Conn 是一个接口,因为这样很容易测试使用网络进行通信的程序。您可以通过虚拟实现其方法来模拟此接口,并测试您的网络协议是否运行正常。

net.Conn通过使用标准库中的方法,您可以准备好使用真正的实现:

net.Dial - 这个方法会返回我们可以用来与远程服务器交谈的连接对象
net.Listener.Accept() - 此方法将返回代表连接到服务器的客户端的连接。方法Accept()的定义interface Listener和它的工作方式取决于这个接口的实现。
http.ResponseWriter
当我们使用HTTP连接时,这个接口最常用。它用于将数据发送回客户端。它有一个简单的定义:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

这三种方法都很容易记住语义:

  • Header() - 它提供了设置自定义HTTP标头的功能:

    func handler(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-Type", "text/plain")
    }
    
  • Write() - 将响应主体发送给客户端:

    func handler(w http.ResponseWriter, req *http.Request) {
        w.Write([]byte("Test"))
    }
    
  • WriteHeader()- 设置HTTP响应状态码(例如200404):

    func handler(w http.ResponseWriter, req *http.Request) {
        w.WriteHeader(http.StatusOK)
    }
    

接口ResponseWriter可以使用httptest.ResponseRecorder struct [ doc ] 来模拟,这是它的一个实现。这样,在Golang中测试HTTP服务器非常简单。

image.Image

该界面表示只读图像。您可以在给定的坐标处读取颜色数据。

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

这个界面非常简单,有三种方法:

  • ColorModel() - 返回有关图像使用的颜色空间的信息(例如,RGBA)
  • Bounds() - 返回图像尺寸数据
  • At() 返回给定坐标处的颜色信息

draw.Image

该界面代表可以修改的图像。它将新方法添加到 image.Image接口中。

type Image interface {
    image.Image
    Set(x, y int, c color.Color)
}

Set()方法可用于修改给定坐标下的颜色数据。

driver.Conn(SQL)[ doc ]

该接口用于各种SQL服务器连接实现。

type Conn interface {
    Prepare(query string) (Stmt, error)
    Close() error
    Begin() (Tx, error)
}

大多数情况下,您不需要使用此接口,因为它是为SQL驱动程序开发人员创建的。与Golang中的SQL服务器的正常连接将涉及为给定的SQL服务器类型(例如Postgresql,MySQL)实现的sql.Open函数和sql.BD结构driver.Conn

sort.Interface [ doc ]

该接口用于定义比较数据类型的方法。

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

它有三种方法:

  • Len() - 返回集合的大小
  • Less() - 告诉给定索引中的一个元素是否比其他元素小
  • Swap() - 用于在集合中的给定索引处交换元素

如果你希望你的集合可以通过标准的Golang函数进行排序,你必须sort.Interface为它创建适当的实现。

结论

这篇文章列出了Golang中一些最重要的接口。当然,这个列表并不完整,因为Go中有更多的接口。这篇文章中的内容是一个很好的起点,并会告诉你你在处理什么,在大多数时间都很有用。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 122,055评论 17 134
  • 01.{ 换行: Opening Brace Can't Be Placed on a Separate Lin...
    田园码农阅读 1,810评论 0 14
  • fmt格式化字符串 格式:%[旗标][宽度][.精度][arg索引]动词旗标有以下几种:+: 对于数值类型总是输出...
    皮皮v阅读 745评论 0 3
  • 敬畏—进入—体验—交给—持续 1,缺啥补啥,怕啥练啥; 2,一切为我所用,所用为团队家; 3,我想变,我要变,我...
    GL_212a阅读 32评论 0 0
  • 37-justoneheart-2018.01.22 1.和伴侣交流从来没有用过“亲爱的”这种昵称,以前会觉得肉麻...
    Justoneheart阅读 27评论 0 0