[翻譯] effective go 之 Methods,Interfaces

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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 printingfunc (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 intfunc (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.Requestfunc (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伺服器 可以把這些整合起來的原因就是 介面是一組方法 幾乎可以給任何類型定義方法

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.