【GoLang筆記】淺析Go語言Interface類型的文法行為及用法

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

Go不是一種典型的OO語言,它在文法上不支援類和繼承的概念。
沒有繼承是否就無法擁有多態行為了呢?答案是否定的,Go語言引入了一種新類型—Interface,它在效果上實現了類似於C++的“多態”概念,雖然與C++的多態在文法上並非完全對等,但至少在最終實現的效果上,它有多態的影子。
那麼,Go的Interface類型到底是什麼呢?怎麼使用呢?這正是本篇筆記試圖說明的問題。

1. Method(s) in Go
在說明Interface類型前,不得不先用Go的method(s)概念來熱身,因為Go語言的interface與method(s)這兩個文法有非常緊密的聯絡。
雖然Go語言沒有類的概念,但它支援的資料類型可以定義對應的method(s)。 本質上說,所謂的method(s)其實就是函數,只不過與普通函數相比,這類函數是作用在某個資料類型上的,所以在函數簽名中,會有個receiver來表明當前定義的函數會作用在該receiver上。
關於methods的精確文法規範,可以參考language specification或Effective Go中的說明,這裡略過。
注意:Go語言支援的除Interface類型外的任何其它資料類型都可以定義其method(而並非只有struct才支援method),只不過實際項目中,method(s)多定義在struct上而已。
在struct類型上定義method(s)的文法特性與C++中的struct支援的文法非常類似(c++中的struct定義了資料,此外也支援定義資料的操作方法), 從這一點來看,我們可以把Go中的struct看作是不支援繼承行為的輕量級的“類”

2. What is Interface type in Go ?
GoLang官網language specification文檔對interface type的概念說明如下:
An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.
說實話,這段說明對新手來說比較晦澀,這正是本篇筆記試圖解釋清楚的地方。
從文法上看,Interface定義了一個或一組method(s),這些method(s)只有函數簽名,沒有具體的實現代碼(有沒有聯想起C++中的虛函數?)。若某個資料類型實現了Interface中定義的那些被稱為"methods"的函數,則稱這些資料類型實現(implement)了interface。舉個例子來說明。

package mainimport (    "fmt"    "math")type Abser interface {    Abs() float64}type MyFloat float64func (f MyFloat) Abs() float64 {    if f < 0 {        return float64(-f)    }    return float64(f)}func main() {    var a Abser    f := MyFloat(-math.Sqrt2)    a = f  // a MyFloat implements Abser    fmt.Println(a.Abs())}
上面的代碼中,第8-10行是通過type文法聲明了一個名為Abser的interface類型(Go中約定的interface類型名通常取其內部聲明的method名的er形式)。而第12-19行通過type文法聲明了MyFloat類型且為該類型定義了名為Abs()的method。
根據上面的解釋,Abs()是interface類型Abser定義的方法,而MyFloat實現了該方法,所以,MyFloat實現了Abser介面。
Interface類型的更通用定義可歸納如下:
type Namer interface {    Method1(param_list) return_type    Method2(param_list) return_type    ...}
上面的樣本用type文法聲明了一個名為Namer的interface類型(但Namer不是個具體的變數,此時記憶體中還沒有它對應的對象)。interface類型是可以定義變數的,也即interface type can have values,例如:
var ai Namer
此時,定義了一個變數名為ai的Namer類型變數, 在Go的底層實現中,ai本質上是個指標,其記憶體布局如下(記憶體布局圖引用自<The Way to Go - A Thorough Introduction to the Go Programming Language>一書第11.1節):

它的method table ptr是不是與C++中類的虛函數表非常類似? 而這正是interface類型的變數具有多態特性的關鍵:
ai共佔2個機器字,1個為receiver欄位,1個為method table ptr欄位。ai可以被賦值為任何變數,只要這個變數實現了interface定義的method(s) set,賦值後,ai的receiver欄位用來hold那個變數或變數副本的地址(若變數類型小於等於1個機器字大小,則receiver直接儲存那個變數;若變數類型大於1個機器字,則Go底層會在堆上申請空間儲存那個變數的副本,然後receiver儲存那個副本的地址,即此時receiver是個指向變數副本的指標)。而由變量實現的介面method(s)組成的interface table的指標會填充到ai的method table ptr欄位。當ai被賦值為另一個變數後,其receiver和method table ptr會更新為新變數的相關值。
關於interface類型內部實現細節,可以參考GoLang官網Blog推薦過的一篇文章“Go Data Structures: Interfaces”,寫的很清楚,強烈推薦。
所以,如果某個函數的入參是個interface類型時,任何實現了該interface的變數均可以作為合法參數傳入且函數的具體行為會自動作用在傳入的這個實現了interface的變數上,這不正是類似於C++中多態的行為嗎?
這正是Interface類型在Go語言中的威力。
引用<The Way to Go>一書第11.5節對interface類型的總結如下,值得每個Go學習者理解:
An interface is a kind of contract which the implementing type(s) must fulfill. Interfaces describe the behaviorof types, what they can do. They completely separate the definition of what an object can do from how it does it, allowing distinct implementations to be represented at different times by the same interface variable, which is what polymorphism essentially is.
Writing functions so that they accept an interface variable as a parameter makes them more general.

3. Interface“多態”特性執行個體
Go語言內建的標準Packages提供的介面很多都藉助了Interface以具備“可以處理任何未知資料類型”的能力。例如被廣泛使用的fmt包,其功能描述如下:
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler.
它除了可以格式化列印Go的built-in類型外,還可以正確列印各種自訂類型,只要這些自訂資料類型實現了fmt的Print API入參所需的interface介面。
以fmt包的Printf()函數為例,其函數簽名格式如下:

func Printf(format string, a ...interface{}) (n int, err error)
它的入參除了用以描述如何格式化的'format'參數外,還需要interface類型的可變長參數。該函數在實現底層的列印行為時,要求傳入的可變長參數實現了fmt包中定義的Stringer介面,這個介面類型定義及描述如下:
type Stringer interface {        String() string}
Stringer is implemented by any value that has a String method, which defines the “native” format for that value. The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.
所以,自訂類型想要調用fmt.Printf()做格式化列印,那隻需實現Stringer介面就行。
例如,下面是一段簡單的列印代碼:
package mainimport "fmt"type IPAddr [4]bytefunc main() {    addrs := map[string]IPAddr{        "loopback":  {127, 0, 0, 1},        "googleDNS": {8, 8, 8, 8},    }    for n, a := range addrs {        fmt.Printf("%v: %v\n", n, a)    }}
其輸出如下:
loopback: [127 0 0 1]googleDNS: [8 8 8 8]
現在要求按規定的格式列印:IPAddr{1, 2, 3, 4}應該輸出為"1.2.3.4"的格式,所以IPAddr這個自訂類型需要實現Stringer介面,實現代碼如下:
// exercise-stringer.gopackage mainimport "fmt"type IPAddr [4]byte// TODO: Add a "String() string" method to IPAddr.func (ip IPAddr) String() string {        return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])}func main() {    addrs := map[string]IPAddr{        "loopback":  {127, 0, 0, 1},        "googleDNS": {8, 8, 8, 8},    }    for n, a := range addrs {        fmt.Printf("%v: %v\n", n, a)    }}

執行結果符合需求:

googleDNS: 8.8.8.8loopback: 127.0.0.1

【參考資料】
1. Golang Language Specification - Methods Expression 
2. Golang Language Specification - Interface Type 
3. <The Way to Go - A Thorough Introduction to the Go Programming Language>一書第11.1節
4. Go Data Structures: Interfaces
5. Go Package fmt 
6. A Tour of Go - Exercise: Stringers 

===================== EOF ====================


相關文章

聯繫我們

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