[翻译] effective go 之 Methods,Interfaces

pengfei_xue · · 3527 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Methods

Pointers vs. Values

Methods can be defined for any named type that is not a pointer or an interface; the receiver does not have to be a struct.

除了指针和接口(interface) 可以为任意自定义的类型定义方法 而且这个自定义类型可以不是结构体struct


In the discussion of slices above, we wrote an Append function. We can define it as a method on slices instead. To do this, we first declare a named type to which we can bind the method, and then make the receiver for the method a value of that type.

在讲slice时 我们写了一个Append函数 当时我们是把需要修改的slice以参数形式传递给函数Append 我们也可以为slice定义方法 但是需要为slice定义别名 并且定义方法的作用对象为该类型 这样可以直接通过slice.Append来给slice添加元素了:

type ByteSlice []byte // 起别名

func (slice ByteSlice) Append(data []byte) []byte {
    // Body exactly the same as above
}

This still requires the method to return the updated slice. We can eliminate that clumsiness by redefining the method to take a pointer to a ByteSlice as its receiver, so the method can overwrite the caller's slice.

上面代码给ByteSlice定义了方法 但还是需要返回修改过的slice 为了避免来回地传递slice 我们可以为ByteSlice的指针类型定义方法:

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // Body as above, without the return.
    *p = slice
}


In fact, we can do even better. If we modify our function so it looks like a standard Write method, like this,

当然了 这里还是有改进空间的 我们可以实现io.Writer接口:

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // Again as above.
    *p = slice
    return len(data), nil
}

then the type *ByteSlice satisfies the standard interface io.Writer, which is handy. For instance, we can print into one.

实现了io.Writer接口后 我们可以这样使用它:

    var b ByteSlice
    fmt.Fprintf(&b, "This hour has %d days\n", 7)

We pass the address of a ByteSlice because only *ByteSlice satisfies io.Writer. The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. This is because pointer methods can modify the receiver; invoking them on a copy of the value would cause those modifications to be discarded.

必须传递指针 这样才能满足io.Writer接口 在定义方法时 如果方法作用对象是某类型的值 则它可以通过该类型值 或者 该类型指针来调用 但是 定义方法时 作用对象是某类型指针 那么只有通过该类型指针才能触发这方法调用 

By the way, the idea of using Write on a slice of bytes is implemented by bytes.Buffer.

bytes slice已经实现了Writer接口 参看bytes.Buffer


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可以通过接口来获得面向对象的能力 比如 通过给类型定义String方法 就可以自定义其输出的格式 Go中经常可以看到只定义了一两个方法的接口 这些接口的名字和它定义的方法相关 比如 io.Writer接口 实现了Write方法 而接口名为io.Writer


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包中的sort.Interface接口 它就可以直接使用sort包的相关函数 同时也可以实现String函数 定制输出的格式

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]
} // Method for printing - sorts the elements before printing. 
func (s Sequence) String() string {
    sort.Sort(s)
    str := "["
    for i, elem := range s {
        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. We can share the effort if we convert the Sequence to a plain []int before callingSprint.

上面例子中String方法重复了Sprint的工作 Sprint已经为slice定义了输出方法 我们可以把Sequence类型转换成[]int 然后直接使用Sprint来输出

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

The conversion causes s to be treated as an ordinary slice and therefore receive the default formatting. Without the conversion, Sprint would find the String method of Sequence and recur indefinitely. 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.)

上述代码中的类型转换导致s被当成是普通的slice来处理 从而在输出时采用默认的格式 如果不做转换 Sprint会去调用Sequence的String方法 然后就进入死循环了 Sequence和[]int除了名字不同 其它都一样 所以在它们之间做转换是可行的 而且是安全的 转换操作并没有创建新的值 它只会临时地把当前的类型当成是另一种类型来处理罢了  


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 {
    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.

这里例子中 我们不需要自己实现多个接口 比如 排序和输出 我们可以利用类型转换来使用其它类型的方法 


Generality

If a type exists only to implement an interface and has no exported methods beyond that interface, there is no need to export the type itself. Exporting just the interface makes it clear that it's the behavior that matters, not the implementation, and that other implementations with different properties can mirror the behavior of the original type. 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.

这样的话 构造函数需要返回接口 而不是实现接口的类型 举个例子吧 哈希库crc32.NewIEEE和adler32.New都返回接口类型hash.Hash32 如果要用Adler32来替换CRC32算法 只需要调用另一个构造函数就可以了 其它的代码都不需要改动


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/cipher包中的Block接口 定义了block加密算法的行为(加密单个数据块的方法)和bufio包类似 cipher包可以实现这个接口来引导流加密算法(Stream接口)注:这里需要看一下加密算法 大部分忘记了

The crypto/cipher interfaces look like this:

crypto/cipher接口长得像下面这个样子 

type Block interface {
    BlockSize() int
    Encrypt(src, dst []byte)
    Decrypt(src, dst []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的定义 它把block cipher转换成stream cipher

// 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或者Stream接口的类型上 由于返回的是接口值 替换CRT加密算法只需要在一个地方改动就可以了 


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.

任意类型都可以定义方法 http包就是一个很好的例子 它定义了Handler接口 任何实现了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本身就是一个接口 它提供了响应客户请求的方法 这些方法包括标准的Writer方法 所以任何可以使用io.Writer的地方都可以使用http.ResponseWriter Request是一个结构体 它包含了已经解析过的HTTP请求


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 but complete implementation of a handler to count the number of times the page is visited.

假设需要处理的HTTP请求只有GET方法 这个假设并不会影响http请求处理函数的实现 下面这段代码很简单 但是它可以处理http请求 记录页面被访问的次数

// 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.) For reference, here's how to attach such a server to a node on the URL tree.

下面这段代码给把/counter映射到特定我们刚定义的Handler上:

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来实现:

// 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.

如果我们想在访问http://host/args时 给出启动server的参数 获得命令参数的函数原型如下:

func ArgServer() {
    for _, s := range os.Args {
        fmt.Println(s)
    }
}


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请求呢 有中做法就是 随便定义个类型 比如是空的结构体 然后像上面那段代码那样实现Handler接口 但是 之前我们已经知道 可以给除了指针和接口外的任意类型定义方法 函数也是一种类型 我们可以直接给函数定义方法 下面这段代码可以在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(c, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
    f(w, req)
}

HandlerFunc是个适配器 可以让普通的函数来处理HTTP请求 如果f是一个函数 那么HandlerFunc(f)则是Handler对象 这个对象会调用f

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是一个函数类型 该类型的值可以处理http请求 看一下它的实现 它作用于函数f  然后在方法里调用f函数 这看起来有点奇怪 但是本质上和其它的类型方法是一样的


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

把ArgServer改写成HTTP服务器 我们首先得改一下它的函数签名 不然HandlerFunc适配不了啊

// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
    for _, s := range os.Args {
        fmt.Fprintln(w, s)
    }
}


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 accessIntSlice.Sort. The code to set it up is concise:

改写后 ArgServer的签名和HandlerFunc一样了 可以把它转换成HandlerFunc类型 来使用HandlerFunc的方法:

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(c, req) inside HandlerFunc.ServeHTTP). The arguments will then be displayed.

当访问http://host/args时 处理这个URL的handler的值就是ArgServer 类型是HandlerFunc HTTP服务器会调用HandlerFunc类型的ServerHTTP方法 


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服务器 可以把这些整合起来的原因就是 接口是一组方法 几乎可以给任何类型定义方法


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:pengfei_xue

查看原文:[翻译] effective go 之 Methods,Interfaces

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3527 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传