[翻譯] effective go 之 Methods,Interfaces

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

除了指針和接口(interface) 能夠爲任意自定義的類型定義方法 並且這個自定義類型能夠不是結構體struct web


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添加元素了: express

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接口: app

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

實現了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服務器 能夠把這些整合起來的緣由就是 接口是一組方法 幾乎能夠給任何類型定義方法

相關文章
相關標籤/搜索