深入理解Go的interface

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

Understanding Go Interface

0. 引言

在 Golang 中,interface 是一個非常重要的概念和特性,之前寫過兩篇相關的文章:Golang “泛型程式設計”,談一談 Golang 的 interface 和 reflect。然後在 Gopher China 2017 的會上又有一個關於 interface 的 topic: understanding golang interface(Gopher China) — youtube,作者是 Francesc。故在此做一個整理和補充。

1. What is Interface?

In object-oriented programming, a protocol or interface is a common means for unrelated objects) to communicate with each other. These are definitions of methods) and values which the objects agree upon in order to co-operate.   — wikipedia

這是 wikipedia 關於 protocal 的定義,將 interface 類比如 protocal 是一種非常助於理解的方式。protocol,中文一般叫做協議,比如網路傳輸中的 TCP 協議。protocol 可以認為是一種雙方為了交流而做出的約定,interface 可以類比如此。

在 Golang 中,interface 是一種抽象類別型,相對於抽象類別型的是具體類型(concrete type):int,string。如下是 io 包裡面的例子。

// Writer is the interface that wraps the basic Write method.//// Write writes len(p) bytes from p to the underlying data stream.// It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// Write must return a non-nil error if it returns n < len(p).// Write must not modify the slice data, even temporarily.//// Implementations must not retain p.type Writer interface {  Write(p []byte) (n int, err error)}// Closer is the interface that wraps the basic Close method.//// The behavior of Close after the first call is undefined.// Specific implementations may document their own behavior.type Closer interface {  Close() error}

在 Golang 中,interface 是一組 method 的集合,是 duck-type programming 的一種體現。不關心屬性(資料),只關心行為(方法)。具體使用中你可以自訂自己的 struct,並提供特定的 interface 裡面的 method 就可以把它當成 interface 來使用。下面是一種 interface 的典型用法,定義函數的時候參數定義成 interface,調用函數的時候就可以做到非常的靈活。

type MyInterface interface{  Print()}func TestFunc(x MyInterface) {}type MyStruct struct {}func (me *MyStruct) Print() {}func main() {      var me Mystruct  TestFunc(me)}

2. Why Interface

Gopher China 上給出了下面三個理由:

  • writing generic algorithm (泛型程式設計)

  • hiding implementation detail (隱藏具體實現)

  • providing interception points (不知道如何翻譯)

2.1 writing generic algorithm

嚴格來說,在 Golang 中並不支援泛型程式設計。在 C++ 等進階語言中使用泛型程式設計非常的簡單,所以泛型程式設計一直是 Golang 詬病最多的地方。但是使用 interface 我們可以實現泛型程式設計,我這裡簡單說一下,具體可以參考我前面給出來的那篇文章。比如我們現在要寫一個泛型演算法,形參定義採用 interface 就可以了,以標準庫的 sort 為例。

package sort// A type, typically a collection, that satisfies sort.Interface can be// sorted by the routines in this package.  The methods require that the// elements of the collection be enumerated by an integer index.type Interface interface {  // Len is the number of elements in the collection.  Len() int  // Less reports whether the element with  // index i should sort before the element with index j.  Less(i, j int) bool  // Swap swaps the elements with indexes i and j.  Swap(i, j int)}...// Sort sorts data.// It makes one call to data.Len to determine n, and O(n*log(n)) calls to// data.Less and data.Swap. The sort is not guaranteed to be stable.func Sort(data Interface) {  // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.  n := data.Len()  maxDepth := 0  for i := n; i > 0; i >>= 1 {    maxDepth++  }  maxDepth *= 2  quickSort(data, 0, n, maxDepth)}

Sort 函數的形參是一個 interface,包含了三個方法:Len()Less(i,j int)Swap(i, j int)。使用的時候不管數組的元素類型是什麼類型(int, float, string…),只要我們實現了這三個方法就可以使用 Sort 函數,這樣就實現了“泛型程式設計”。有一點比較麻煩的是,我們需要將數組自訂一下。下面是一個例子。

type Person struct {  Name stringAge  int}func (p Person) String() string {  return fmt.Sprintf("%s: %d", p.Name, p.Age)}// ByAge implements sort.Interface for []Person based on// the Age field.type ByAge []Person //自訂func (a ByAge) Len() int {   return len(a) }func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i]}func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }func main() {  people := []Person{   {"Bob", 31},   {"John", 42},   {"Michael", 17},   {"Jenny", 26},  }  fmt.Println(people)  sort.Sort(ByAge(people))  fmt.Println(people)}

另外 Fransesc 在 Gopher China 上還提到了一個比較有趣的東西和大家分享一下。在我們設計函數的時候,下面是一個比較好的準則。

Be conservative in what you send, be liberal in what you accept.    — Robustness Principle

對應到 Golang 就是:

Return concrete types, receive interfaces as parameter.   — Robustness Principle applied to Go

話說這麼說,但是當我們翻閱 Golang 源碼的時候,有些函數的傳回值也是 interface。

2.2 hiding implement detail

隱藏具體實現,這個很好理解。比如我設計一個函數給你返回一個 interface,那麼你只能通過 interface 裡面的方法來做一些操作,但是內部的具體實現是完全不知道的。Francesc 舉了個 context 的例子。 context 最先由 google 提供,現在已經納入了標準庫,而且在原有 context 的基礎上增加了:cancelCtx,timerCtx,valueCtx。語言的表達有時候略顯蒼白無力,看一下 context 包的代碼吧。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {    c := newCancelCtx(parent)    propagateCancel(parent, &c)        return &c, func() { c.cancel(true, Canceled) }}

表明上 WithCancel 函數返回的還是一個 Context interface,但是這個 interface 的具體實現是 cancelCtx struct。

// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {       return cancelCtx{        Context: parent,        done:    make(chan struct{}),    }}// A cancelCtx can be canceled. When canceled, it also cancels any children// that implement canceler.type cancelCtx struct {    Context    done chan struct{} // closed by the first cancel call.    mu       sync.Mutex    children map[canceler]struct{} // set to nil by the first cancel call    err      error                 // set to non-nil by the first cancel call}func (c *cancelCtx) Done() <-chan struct{} {    return c.done}func (c *cancelCtx) Err() error {    c.mu.Lock()    defer c.mu.Unlock()    return c.err}func (c *cancelCtx) String() string {    return fmt.Sprintf("%v.WithCancel", c.Context)}

儘管內不是實現上下面上個函數返回的具體 struct (都實現了 Context interface)不同,但是對於使用者來說是完全無感知的。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtxfunc WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtxfunc WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx

2.3 providing interception points

Francesc 這裡的 interception 想表達的意思我理解應該是 wrapper 或者裝飾器,他給出了一個例子如下:

type header struct {  rt  http.RoundTripper  v   map[string]string}func (h header) RoundTrip(r *http.Request) *http.Response {  for k, v := range h.v {    r.Header.Set(k,v)  }  return h.rt.RoundTrip(r)}

通過 interface,我們可以通過類似這種方式實現 dynamic dispatch。

3. 非侵入式

Francesc 還提到 interface 的非侵入式特性。什麼是侵入式呢?比如 Java 的 interface 實現需要顯示的聲明。

public class MyWriter implements io.Writer {}

這樣就意味著如果要實現多個 interface 需要顯示地寫很多遍,同時 package 的依賴還需要進行管理。Dependency is evil。比如我要實現 io 包裡面的 Reader,Writer,ReadWriter 介面,代碼可以像下面這樣寫。

type MyIO struct {}func (io *MyIO) Read(p []byte) (n int, err error) {...}func (io *MyIO) Write(p []byte) (n int, err error) {...}// io packagetype Reader interface {    Read(p []byte) (n int, err error)}type Writer interface {    Write(p []byte) (n int, err error)}type ReadWriter interface {    Reader    Writer}

這種寫法真的很方便,而且不用去顯示的 import io package,interface 底層實現的時候會動態檢測。這樣也會引入一些問題:

  1. 效能下降。使用 interface 作為函數參數,runtime 的時候會動態確定行為。而使用 struct 作為參數,編譯期間就可以確定了。

  2. 不知道 struct 實現哪些 interface。這個問題可以使用 guru 工具來解決。

綜上,Golang interface 的這種非侵入實現真的很難說它是好,還是壞。但是可以肯定的一點是,對開發人員來說代碼寫起來更簡單了。

4. interface type assertion

interface 像其他類型轉換的時候一般我們稱作斷言,舉個例子。

func do(v interface{}) {  n := v.(int)    // might panic}

這樣寫的壞處在於:一旦宣告失敗,程式將會 panic。一種避免 panic 的寫法是使用 type assertion。

func do(v interface{}) {  n, ok := v.(int)  if !ok {  // 宣告失敗處理  }}

對於 interface 的操作可以使用 reflect 包來處理,關於 reflect 包的原理和使用可以參考我的文章。

5. 總結

interface 是 Golang 的一種重要的特性,但是這是以 runtime 為代價的,也就意味著效能的損失(關於 interface 的底層實現之後又時間再寫)。拋開效能不談(現實中使用 Golang 開發的程式 99% 效能都不是問題),interface 對於如何設計我們的代碼確實給了一個很好的思考。

6. 參考

1. [Golang "泛型程式設計"](http://legendtkl.com/2015/11/25/go-generic-programming/)

2. [談一談 Golang 的 interface 和 reflect](http://legendtkl.com/2015/11/28/go-interface-reflect/)

3. [understanding golang interface(Gopher China) — youtube](https://www.youtube.com/watch?v=F4wUrj6pmSI&t=2319s)

4. [understanding golang interface(Gopher China) — slide](https://github.com/gopherchina/conference/blob/master/2017/1.4%20interface.presented.pdf)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.