Skip to content

Latest commit

 

History

History
332 lines (231 loc) · 19.9 KB

11.Interfaces-and-other-types.md

File metadata and controls

332 lines (231 loc) · 19.9 KB

Interfaces and other types

Interfaces

Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here. We've seen a couple of simple examples already; custom printers can be implemented by a String method while Fprintf can generate output to anything with a Write method. Interfaces with only one or two methods are common in Go code, and are usually given a name derived from the method, such as io.Writer for something that implements Write.

Go 的 Interface 提供了一种指定对象行为的方法:如果某个对象可以如此如此......那么它就可以被用在此处。这样的示例我们已见过两个了:String 方法可以实现自定义的 printer,Fprintf 可以使用任意实现了 Write 方法的对象来生成输出。拥有一到两个方法的接口在 Go 中非常普遍,且通常被赋予一个由方法派生的名称,例如 io.Writer 表示某对象实现了 Write 方法。

A type can implement multiple interfaces. For instance, a collection can be sorted by the routines in package sort if it implements sort.Interface, which contains Len(), Less(i, j int) bool, and Swap(i, j int), and it could also have a custom formatter. In this contrived example Sequence satisfies both.

一个类型可以实现多个接口。例如,如果一个集合实现了 sort.Interface(包括 Len(),Less(i, j int) bool,和 Swap(i, j int) ),那么它即可以用 sort 包中的函数来排序,也可以拥有自定义的格式化输出。下面 Sequence 的示例略显牵强,但它同时实现了这两个接口。

type Sequence []int

// Methods required by sort.Interface.
func (s Sequence) Len() int {
    return len(s)
}
func (s Sequence) Less(i, j int) bool {
    return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

// Copy returns a copy of the Sequence.
func (s Sequence) Copy() Sequence {
    copy := make(Sequence, 0, len(s))
    return append(copy, s...)
}

// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
    s = s.Copy() // Make a copy; don't overwrite argument.
    sort.Sort(s)
    str := "["
    for i, elem := range s { // Loop is O(N²); will fix that in next example.
        if i > 0 {
            str += " "
        }
        str += fmt.Sprint(elem)
    }
    return str + "]"
}

Conversions

The String method of Sequence is recreating the work that Sprint already does for slices. (It also has complexity O(N²), which is poor.) We can share the effort (and also speed it up) if we convert the Sequence to a plain []int before calling Sprint.

Sequence 的 String 方法重复了 Sprint 打印 slice 的工作。(且时间复杂度为 O(N²),性能低下)我们可以将 Sequence 转化为普通的 []int 来共用 Sprint 的代码,且能优化执行效率。

func (s Sequence) String() string {
    s = s.Copy()
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}

This method is another example of the conversion technique for calling Sprintf safely from a String method. Because the two types (Sequence and []int) are the same if we ignore the type name, it's legal to convert between them. The conversion doesn't create a new value, it just temporarily acts as though the existing value has a new type. (There are other legal conversions, such as from integer to floating point, that do create a new value.)

此方法是又一个在 String 方法中安全调用 Sprintf 的转换技术示例。如果我们忽略类型的名称,Sequence 和 []int 是两种相同的类型,彼此之间可以合法转换。这个转换不会创建新的值,仅仅暂时表现的好像有一个新值。(还有其它一些合法的转换,比如从 integer 到 float,这种转换会创建新的值)

It's an idiom in Go programs to convert the type of an expression to access a different set of methods. As an example, we could use the existing type sort.IntSlice to reduce the entire example to this:

在 Go 中,一种地道的编程方式是转换一个表达式类型以访问不同的方法集合。例如,我们可以使用已存在的 sort.IntSlice 类型来大大简化我们之前的示例:

type Sequence []int

// Method for printing - sorts the elements before printing
func (s Sequence) String() string {
    s = s.Copy()
    sort.IntSlice(s).Sort()
    return fmt.Sprint([]int(s))
}

Now, instead of having Sequence implement multiple interfaces (sorting and printing), we're using the ability of a data item to be converted to multiple types (Sequence, sort.IntSlice and []int), each of which does some part of the job. That's more unusual in practice but can be effective.

现在,我们不必实现所有的接口(sorting and printing),而是利用数据转换的能力来访问不同类型的功能(Sequence, sort.IntSlice and []int)。这在实践中很常用,且高效。

Interface conversions and type assertions

Type switches are a form of conversion: they take an interface and, for each case in the switch, in a sense convert it to the type of that case. Here's a simplified version of how the code under fmt.Printf turns a value into a string using a type switch. If it's already a string, we want the actual string value held by the interface, while if it has a String method we want the result of calling the method.

Type switches 其实是一种转换:它们接受一个 interface,某种程度上讲,switch 的每个分支都将 interface 转换为自己的类型。此处有一例,展示了 fmt.Printf 的底层代码是如何使用 type switch 将一个值转化为字符串的。如果它本身就是一个字符串,那么直接返回;如果它有 String 方法,则返回其方法调用的结果。

type Stringer interface {
    String() string
}

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str
case Stringer:
    return str.String()
}

The first case finds a concrete value; the second converts the interface into another interface. It's perfectly fine to mix types this way.

第一个分支寻求一个具体的值;第二个则将其转换为另一个 interface。以这种方式混合类型是完全没问题的。

What if there's only one type we care about? If we know the value holds a string and we just want to extract it? A one-case type switch would do, but so would a type assertion. A type assertion takes an interface value and extracts from it a value of the specified explicit type. The syntax borrows from the clause opening a type switch, but with an explicit type rather than the type keyword:

假如我们只关心一种类型又该如何呢?如果我们已知一个值持有一个字符串,我们只是想提取出来呢?一个分支的 type switch 固然可以做到,而类型断言同样可以。类型断言接受一个 interface 值,并从中提取一个指定类型的值。其语法借鉴自 type switch 的开端,但使用了一个明确的类型而非关键字 type :

value.(typeName)

and the result is a new value with the static type typeName. That type must either be the concrete type held by the interface, or a second interface type that the value can be converted to. To extract the string we know is in the value, we could write:

相应地,结果是一个拥有静态类型 typeName 的新值。此类型必须为一个 interface 持有的具体类型,或者是另一个可为之转换的 interface 类型。从中提取一个我们已知的字符串,可以作如下写法:

str := value.(string)

But if it turns out that the value does not contain a string, the program will crash with a run-time error. To guard against that, use the "comma, ok" idiom to test, safely, whether the value is a string:

但是,如果断言的值不包含字符串的话,程序将会因运行时错误而崩溃。安全起见,使用 “comma, ok” 的编码风格来测试其是否包含 string:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

If the type assertion fails, str will still exist and be of type string, but it will have the zero value, an empty string.

若断言失败,str 仍旧存在且为 string 类型,只不过是一个零值,一个空字符串。

As an illustration of the capability, here's an if-else statement that's equivalent to the type switch that opened this section.

下面是一个与 type switch 等效的 if-else 句法,用于阐释其功用。

if str, ok := value.(string); ok {
    return str
} else if str, ok := value.(Stringer); ok {
    return str.String()
}

Generality

If a type exists only to implement an interface and will never have exported methods beyond that interface, there is no need to export the type itself. Exporting just the interface makes it clear the value has no interesting behavior beyond what is described in the interface. It also avoids the need to repeat the documentation on every instance of a common method.

如果一个类型只是为了实现接口而存在,并且永远不会有接口之外的导出方法,则不需要导出该类型。仅导出接口方法的目的显而易见,即该值无接口以外的其它行为。同时,这也避免了为每个相同方法的实例重复编写文档。

In such cases, the constructor should return an interface value rather than the implementing type. As an example, in the hash libraries both crc32.NewIEEE and adler32.New return the interface type hash.Hash32. Substituting the CRC-32 algorithm for Adler-32 in a Go program requires only changing the constructor call; the rest of the code is unaffected by the change of algorithm.

这种情况下,构造器应返回一个接口类型而非接口的实现类型。举例来说,hash 库中 crc32.NewIEEEadler32.New 都返回的是接口类型 hash.Hash32。在 Go 程序中只需要改变构造器的调用就可以将算法从 CRC-32 替换为 Adler-32;其余代码不会因算法更改而受到影响。

A similar approach allows the streaming cipher algorithms in the various crypto packages to be separated from the block ciphers they chain together. The Block interface in the crypto/cipher package specifies the behavior of a block cipher, which provides encryption of a single block of data. Then, by analogy with the bufio package, cipher packages that implement this interface can be used to construct streaming ciphers, represented by the Stream interface, without knowing the details of the block encryption.

照此方法,可使 crypto 包中的流密码算法与其使用的块密码算法互相剥离。crypto/cipher 包中的 Block 接口规定了块密码的行为,其中包括对一个数据块进行加密。然后,类似于 bufio 包,实现 Block 接口的密码包可用于构造流式处理密码(由 Stream 接口表示),而无需了解块加密的详细信息。

译注: Stream 接口的实现者,如 cfb 、ofb 都在其内部使用了 Block 接口;类似的 bufio 包中的 Reader 和 Writer 都在其内部使用了 io.Reader 和 io.Writer 。共同点是都无需知道接口的内部实现细节,只用其行为。

The crypto/cipher interfaces look like this:

crypto/cipher 包中的接口如下所示:

type Block interface {
    BlockSize() int
    Encrypt(dst, src []byte)
    Decrypt(dst, src []byte)
}

type Stream interface {
    XORKeyStream(dst, src []byte)
}

Here's the definition of the counter mode (CTR) stream, which turns a block cipher into a streaming cipher; notice that the block cipher's details are abstracted away:

下面是创建计数器模式 stream 的定义,它将块密码变成流密码;需要注意的是,块密码算法的细节完全被抽象了:

// NewCTR returns a Stream that encrypts/decrypts using the given Block in
// counter mode. The length of iv must be the same as the Block's block size.
func NewCTR(block Block, iv []byte) Stream

NewCTR applies not just to one specific encryption algorithm and data source but to any implementation of the Block interface and any Stream. Because they return interface values, replacing CTR encryption with other encryption modes is a localized change. The constructor calls must be edited, but because the surrounding code must treat the result only as a Stream, it won't notice the difference.

NewCTR 并不局限于某种特定的加密算法和数据源,而是囊括了 Block 接口的所有实现和任意数据流。因为构造器返回的是接口,故替换 CTR 为其他加密模式只是局部改变。构造器的调用肯定需要修改,但因其周围的代码待其返回值以 Stream 接口,所以并不会感到有所不同。

Interfaces and methods

Since almost anything can have methods attached, almost anything can satisfy an interface. One illustrative example is in the http package, which defines the Handler interface. Any object that implements Handler can serve HTTP requests.

由于几乎所有的事物都能附加方法,所以几乎所有的事物都能实现接口。定义了 Handler 接口的 http 包即是一个很好的示例。任何实现了 Handler 接口的对象都可以服务于 HTTP 请求。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ResponseWriter is itself an interface that provides access to the methods needed to return the response to the client. Those methods include the standard Write method, so an http.ResponseWriter can be used wherever an io.Writer can be used. Request is a struct containing a parsed representation of the request from the client.

ResponseWriter 本身即是一个接口,它提供了响应客户端的方法。这些方法中包括标准的 Write 方法,因此 http.ResponseWriter 适用于任何使用 io.Writer 的地方。Request 是一个包含了已解析的客户端请求的结构体。

For brevity, let's ignore POSTs and assume HTTP requests are always GETs; that simplification does not affect the way the handlers are set up. Here's a trivial implementation of a handler to count the number of times the page is visited.

简单起见,让我们先忽略 POST,假设 HTTP 请求都是 GET;这种简化不会影响 handler 的设置方式。下面是一个 handler 的简单实现,用于计算页面的访问次数。

// Simple counter server.
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ctr.n++
    fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

(Keeping with our theme, note how Fprintf can print to an http.ResponseWriter.) In a real server, access to ctr.n would need protection from concurrent access. See the sync and atomic packages for suggestions.

(紧跟我们的思路,注意 Fprintf 是如何打印到 http.ResponseWriter 的),真正编写程序时, 访问 ctr.n 会存在并发问题,请参考 sync 和 atomic 包寻找解决方案。

For reference, here's how to attach such a server to a node on the URL tree.

作为参考,下面介绍了如何将 Counter 附加到 URL 节点上。

import "net/http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

But why make Counter a struct? An integer is all that's needed. (The receiver needs to be a pointer so the increment is visible to the caller.)

不过,为何要将 Counter 设计为一个结构体呢?其实只需一个整型就够了。(接收器需为指针,如此调用者方能看到改变)。

// Simpler counter server.
type Counter int

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    *ctr++
    fmt.Fprintf(w, "counter = %d\n", *ctr)
}

What if your program has some internal state that needs to be notified that a page has been visited? Tie a channel to the web page.

如果程序有些内部状态,需要接收页面已被访问的通知该怎么办呢?绑定一个 channel 到 web 页面上。

// A channel that sends a notification on each visit.
// (Probably want the channel to be buffered.)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ch <- req
    fmt.Fprint(w, "notification sent")
}

Finally, let's say we wanted to present on /args the arguments used when invoking the server binary. It's easy to write a function to print the arguments.

最后,假设我们想在 /args 路径下呈现程序调用的所有参数。当然,写一个函数来打印参数非常简单。

func ArgServer() {
    fmt.Println(os.Args)
}

How do we turn that into an HTTP server? We could make ArgServer a method of some type whose value we ignore, but there's a cleaner way. Since we can define a method for any type except pointers and interfaces, we can write a method for a function. The http package contains this code:

如何才能将其变为一个 HTTP server 呢?我们当然可以把 ArgServer 定义为一个无意义的类型的方法,然而有一个更加干净利落的做法。因为我们可以为除了指针和接口之外的任意类型定义方法,因此可以为一个函数定义一个方法。http 包中有如下代码:

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers.  If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
    f(w, req)
}

HandlerFunc is a type with a method, ServeHTTP, so values of that type can serve HTTP requests. Look at the implementation of the method: the receiver is a function, f, and the method calls f. That may seem odd but it's not that different from, say, the receiver being a channel and the method sending on the channel.

HandlerFunc 是一个拥有 ServeHTTP 方法的类型,它的值可以服务于 HTTP 请求。且看此方法的实现:接收器为函数 f,并且在方法中调用了 f。虽然看起来有些奇怪,但它与之前 Handler 的实现 Chan 没有区别(接收器为 channel,且在方法中向 channel 发送消息)。

To make ArgServer into an HTTP server, we first modify it to have the right signature.

要将 ArgServer 变成 HTTP server,首先需要修其签名。

// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}

ArgServer now has same signature as HandlerFunc, so it can be converted to that type to access its methods, just as we converted Sequence to IntSlice to access IntSlice.Sort. The code to set it up is concise:

现在 ArgServerHandlerFunc 的签名一致,所以可将其转换为 HandlerFunc 来访问其方法,就像我们将 Sequence 转换为 IntSlice 来访问 IntSlice.Sort 那样。设置代码非常简洁:

http.Handle("/args", http.HandlerFunc(ArgServer))

When someone visits the page /args, the handler installed at that page has value ArgServer and type HandlerFunc. The HTTP server will invoke the method ServeHTTP of that type, with ArgServer as the receiver, which will in turn call ArgServer (via the invocation f(w, req) inside HandlerFunc.ServeHTTP). The arguments will then be displayed.

当有人访问页面 /args 时,与其绑定的 handler 是 HandlerFunc 类型的 ArgServer 。HTTP server 将调用该类型的 ServeHTTP 方法,此时接收器为 ArgServer ,所以最终将调用 ArgServer (通过 HandlerFunc.ServeHTTP 内部的 f(w, req) 条用 )。然后,程序的调用参数就会被显示出来。

In this section we have made an HTTP server from a struct, an integer, a channel, and a function, all because interfaces are just sets of methods, which can be defined for (almost) any type.

本章节,我们分别使用结构体、整型、channel和函数来构造 HTTP server,这一切都是因为接口只是方法的集合,你可以为任意类型(几乎)定义方法。