Go語言介面(第一部分)

來源:互聯網
上載者:User
![avatar](https://raw.githubusercontent.com/studygolang/gctt-images/master/interface-in-go/part1-1.jpg)介面提升了代碼的彈性與拓展性,同時它也是 go 語言實現多態的一種方式。介面允許通過一些必要的行為來實現,而不再要求設定特定類型。而這個行為就是通過一些方法設定來定義的:```gotype I interface { f1(name string) f2(name string) (error, float32) f3() int64}```不需要特定的實現。只要通過定義 type 中包含目標名與簽名(輸入與輸出的參數列表)的方法來表明其實現(滿足)了一個介面就足夠了:```gotype T int64func (T) f1(name string) { fmt.Println(name)}func (T) f2(name string) (error, float32) { return nil, 10.2}func (T) f3() int64 { return 10}```類型 T 實現了第一個程式段的介面 I。舉個例子,類型 T 的值可以傳遞給任何接受 I 作為參數的函數([原始碼](https://play.golang.org/p/aUyEa-HgYi)):```gotype I interface { M() string}type T struct { name string}func (t T) M() string { return t.name}func Hello(i I) { fmt.Printf("Hi, my name is %s\n", i.M())}func main() { Hello(T{name: "Michał"}) // "Hi, my name is Michał"}```在 function Hello 中,方法調用了 `i.M()`。 這個過程概括一下就是,只要來自不同 type 的方法是通過 type 來實現 interface I,就可以被調用。go 語言的突出特點就是其 `interface` 是隱式實現的。程式員不需要指定 type T 實現了 interface I。這個工作由 go 的編譯器完成(不需要派一個人去做機器的工作)。這種行為中的實現方式之所以很贊,是因為定義 interface 這件事情是由已經寫好的 type 自動實現的(不需要為之做任何改變)。之所以 interface 可以提供彈性,是因為任意一個 type 可以實現多個 interface ([代碼](https://play.golang.org/p/cN6KrJab-l)):```gotype I1 interface { M1()}type I2 interface { M2()}type T struct{}func (T) M1() { fmt.Println("T.M1") }func (T) M2() { fmt.Println("T.M2") }func f1(i I1) { i.M1() }func f2(i I2) { i.M2() }func main() { t := T{} f1(t) // "T.M1" f2(t) // "T.M2"}```或者同樣的 interface 可以實現多個 type ([原始碼](https://play.golang.org/p/_7mkHdEilz)):```gotype I interface { M()}type T1 struct{}func (T1) M() { fmt.Println("T1.M") }type T2 struct{}func (T2) M() { fmt.Println("T2.M") }func f(i I) { i.M() }func main() { f(T1{}) // "T1.M" f(T2{}) // "T2.M"}```> *而且除了一個或多個 interface 所需要的方法外,type 可以自由地實現其他方法*---在 go 中,我們有兩個與 interface 相關的概念:1. 介面-通過[關鍵字](https://golang.org/ref/spec#Keywords) `interface`,實現此類介面所需要的一組方法;2. 介面類型-介面類型的變數,可以儲存一些實現於特定介面的值。讓我們在接下來的兩節中討論這些主題。## 定義一個介面介面類型的聲明指定屬於它(介面)的方法。方法是通過它的名字(方法名)和簽名-輸入和介面參數定義的:```gotype I interface { m1() m2(int) m3(int) int m4() int}```除了方法外,它還允許嵌入其他介面-在同一個包中定義或引入-通過[限定名](https://golang.org/ref/spec#QualifiedIdent)。它從嵌入的介面中添加所有方法:```goimport "fmt"type I interface { m1()}type J interface { m2() I fmt.Stringer}```介面 J 的方法組包括:* m1() 來自嵌入的介面 I* m2()* String() string(來自嵌入的介面 [Stringer](https://golang.org/pkg/fmt/#Stringer))順序無關緊要,所以方法規格與嵌入的介面類型完全可以交錯。> *添加了來自嵌入介面類型的匯出方法(以大寫字母開頭)和非匯出方法(以小寫字母開頭)*如果我嵌入一個介面 J,介面 J 又嵌入介面 K,那麼 K 中的所有方法也會被添加到 I 中:```gotype I interface { J i()}type J interface { K j()}type K interface { k()}```I 的方法組包括 `i()` ,`j()` 和 `k()` ([原始碼](https://play.golang.org/p/mz_8CMMDsn))。不允許迴圈嵌入介面(譯註:即 A 嵌入 B,B 嵌入 C,C 嵌入 A),並且在編譯階段,會檢測介面的迴圈嵌入問題([原始碼](https://play.golang.org/p/CXf3-quH0A)):```gotype I interface { J i()}type J interface { K j()}type K interface { k() I}```編譯器會提出一個錯誤 `interface type loop involving I`。介面方法必須有唯一名字([原始碼](https://play.golang.org/p/zt3t-GUrYU)):```gotype I interface { J i()}type J interface { j() i(int)}```否則將拋出編譯時間錯誤:`duplicate method i`。介面的組成可以在標準庫中找到。一個這樣的例子就是 io.ReadWriter :```gotype ReadWriter interface { Reader Writer}```我們知道如何建立一個新的介面。現在讓我們學習介面類型的值...## 介面類型的值介面類型 I 的變數可以保持任何實現 I 的值([原始碼](https://play.golang.org/p/Zvaq5c97wp)):```gotype I interface { method1()}type T struct{}func (T) method1() {}func main() { var i I = T{} fmt.Println(i)}```這裡我們有一個來自介面類型 I 的變數 i。### 靜態類型 VS 動態類型在編譯階段,變數類型便已知。這是在聲明時指定的,不再變化,並被稱為靜態類型(或只是類型)。介面類型的變數也有靜態類型,其本身就是一個介面。它們還具有可以指定值的類型-動態類型([原始碼](https://play.golang.org/p/UVMqqMNsb8)):```gotype I interface { M()}type T1 struct {}func (T1) M() {}type T2 struct {}func (T2) M() {}func main() { var i I = T1{} i = T2{} _ = i}```變數 i 的靜態類型是 I。這是不變的。另一方面,動態類型是...好吧,動態。在首次分配後,i 的動態類型是 T1。這並不是一成不變的,所以 i 的動態類型第二次賦值為 T2。當介面類型值的值 nil (介面類型的零值)時,動態類型便不設定。### 如何擷取介面類型值得動態類型?包 [reflect](https://golang.org/pkg/reflect/)可以用來擷取這個([原始碼](https://play.golang.org/p/9cQ5JqSxL5)):```gofmt.Println(reflect.TypeOf(i).PkgPath(), reflect.TypeOf(i).Name())fmt.Println(reflect.TypeOf(i).String())```通過包 [fmt](https://golang.org/pkg/fmt/) 以及格式動詞 `%d` 也可以做到這點:```gofmt.Printf("%T\n", i)```在 hood 下使用包 *reflect* 包,即便 i 是 nil 時,這個方法也有效。### 空介面值這次我們將從一個例子開始([原始碼](https://play.golang.org/p/kv9XUzIxBU)):```gotype I interface { M()}type T struct {}func (T) M() {}func main() { var t *T if t == nil { fmt.Println("t is nil") } else { fmt.Println("t is not nil") } var i I = t if i == nil { fmt.Println("i is nil") } else { fmt.Println("i is not nil") }}```輸出:```t is nili is not nil```第一次看,會覺得很驚訝。變數 i 的值,我們明明設定為 nil,但是這裡的值卻不等於 nil。其實介面類型值包含兩個組件:* 動態類型* 動態值動態類型在之前(“靜態類型VS動態類型”部分)已經討論過了。動態值是指定的實際值。在賦值 `var i I = t` 後的討論段中,i 的動態值是 nil,但動態類型為\**T*在這個複製後,函數調用 `fmt.Printf("%T\n", i)`將會列印 `*main.T`。`若且唯若動態值與動態類型都為 nil 時,介面類型值為 nil。`結果就是即使介面類型值包含一個 nil 指標,這樣的介面值也不是 nil。已知的錯誤就是返回未初始化,從函數返回介面類型為非介面類型值([原始碼](https://play.golang.org/p/4-M35Nc2JZ)):```gotype I interface {}type T struct {}func F() I { var t *T if false { // not reachable but it actually sets value t = &T{} } return t}func main() { fmt.Printf("F() = %v\n", F()) fmt.Printf("F() is nil: %v\n", F() == nil) fmt.Printf("type of F(): %T", F())}```它列印出:```F() = <nil>F() is nil: falsetype of F(): *main.T```只是因為從函數返回的介面類型值有動態類型集(*main.T)。它並不等於 nil。### 空介面介面的方法集不必包含至少一個成員(即方法集為空白)。它完全可以是空的([原始碼](https://play.golang.org/p/V0GEG5nuW3)):```gotype I interface {}type T struct {}func (T) M() {}func main() { var i I = T{} _ = i}```空介面可以自動滿足任意類型-因此任意類型的值都可以賦值給這樣的介面類型值。動態類型或靜態類型的行為應用於空介面,就像應用於非空介面。空介面的顯著使用存在於參數可變函數 [fmt.Println](https://golang.org/pkg/fmt/#Println)。---## 滿足一個介面每個實現了介面所有方法的類型都自動滿足這個介面。我們不需要在這些類型中使用任何其他關鍵字(如 Java中的 implements)來表示該類型實現了介面。它是由 go 語言的編譯器自動實現的,而這兒正是該語言的強大之處([原始碼](https://play.golang.org/p/U4r6i2X5xb)):```goimport ( "fmt" "regexp")type I interface { Find(b []byte) []byte}func f(i I) { fmt.Printf("%s\n", i.Find([]byte("abc")))}func main() { var re = regexp.MustCompile(`b`) f(re)}```這裡我們定義了一個由 [regexp.Regexp](https://golang.org/pkg/regexp/#Regexp) 類型實現的介面,該介面內建的 regexp 模組沒有任何改變。## 行為抽象介面類型值**只**允許訪問它自己的介面類型的方法。如果它是 struct, array, scalar 等,便會隱藏有關確切值的詳情([原始碼](https://play.golang.org/p/kCjgQFCsL_)):```gotype I interface { M1()}type T int64func (T) M1() {}func (T) M2() {}func main() { var i I = T(10) i.M1() i.M2() // i.M2 undefined (type I has no field or method M2)}```

via: https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c

作者:Michał Łowicki 譯者:cureking 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

109 次點擊  
相關文章

聯繫我們

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