在面向對象編程中,能夠這麼說:「接口定義了對象的行爲」, 那麼具體的實現行爲就取決於對象了。編程
在Go中,接口是一組方法簽名(聲明的是一組方法的集合)。當一個類型爲接口中的全部方法提供定義時,它被稱爲實現該接口。它與oop很是類似。接口指定類型應具備的方法,類型決定如何實現這些方法。函數
讓咱們來看看這個例子: Animal
類型是一個接口,咱們將定義一個 Animal
做爲任何能夠說話的東西。這是 Go 類型系統的核心概念:咱們根據類型能夠執行的操做而不是其所能容納的數據類型來設計抽象。oop
type Animal interface { Speak() string }
Animal
爲任何具備
Speak
方法的類型。
Speak
方法沒有參數,返回一個字符串。
全部定義了該方法的類型咱們稱它實現了 Animal
接口。Go 中沒有
implements
關鍵字,判斷一個類型是否實現了一個接口是徹底是自動地。讓咱們建立幾個實現這個接口的類型:
type Dog struct { } func (d Dog) Speak() string { return "Woof!" } type Cat struct { } func (c Cat) Speak() string { return "Meow!" } type Llama struct { } func (l Llama) Speak() string { return "?????" } type JavaProgrammer struct { } func (j JavaProgrammer) Speak() string { return "Design patterns!" }
咱們如今有四種不一樣類型的動物:
Dog
、
Cat
、
Llama
和
JavaProgrammer
。在咱們的
main
函數中,咱們建立了一個
[]Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
,看看每隻動物都說了些什麼:
func main() { animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}} for _, animal := range animals { fmt.Println(animal.Speak()) } }
interface{}
類型
interface{}
類型,
空接口,是致使不少混淆的根源。
interface{}
類型是沒有方法的接口。因爲沒有
implements
關鍵字,因此全部類型都至少實現了 0 個方法,因此
全部類型都實現了空接口。這意味着,若是您編寫一個函數以
interface{}
值做爲參數,那麼您能夠爲該函數提供任何值。例如:
func DoSomething(v interface{}) { // ... }
DoSomething
函數內部,
v
的類型是什麼?
新手們會認爲 v
是任意類型的,但這是錯誤的。v
不是任意類型,它是 interface{}
類型。對的,沒錯!當將值傳遞給
DoSomething
函數時,Go 運行時將執行類型轉換(若是須要),並將值轉換爲
interface{}
類型的值。全部值在運行時只有一個類型,而
v
的一個靜態類型是
interface{}
。
interface{}
的值呢?(具體到上例來講就是
[]Animal
中存的是啥?)
在咱們上面的例子中,當咱們初始化變量 animals
時,咱們不須要像這樣 Animal(Dog{})
來顯示的轉型,由於這是自動地。這些元素都是 Animal
類型,可是他們的底層類型卻不相同。spa
爲何這很重要呢?理解接口是如何在內存中表示的,可使得一些潛在的使人困惑的事情變得很是清楚。好比,像 「我能夠將 []T 轉換爲 []interface{}
嗎?」 這種問題就容易回答了。下面是一些爛代碼的例子,它們表明了對 interface{}
類型的常見誤解:設計
package main import ( "fmt" ) func PrintAll(vals []interface{}) { for _, val := range vals { fmt.Println(val) } } func main() { names := []string{"stanley", "david", "oscar"} PrintAll(names) }
運行這段代碼你會獲得以下錯誤:cannot use names (type []string) as type []interface {} in argument to PrintAll
。若是想使其正常工做,咱們必須將 []string
轉爲 []interface{}
:指針
package main import ( "fmt" ) func PrintAll(vals []interface{}) { for _, val := range vals { fmt.Println(val) }
} func main() { names := []string{"stanley", "david", "oscar"} vals := make([]interface{}, len(names)) for i, v := range names { vals[i] = v } PrintAll(vals) }
很醜陋,可是生活就是這樣,沒有完美的事情。(事實上,這種狀況不會常常發生,由於 []interface{}
並無像你想象的那樣有用)code
Cat
的
Speak()
方法改成指針接收器:
func (c *Cat) Speak() string { return "Meow!" }
運行上述代碼,會獲得以下錯誤:對象
cannot use Cat literal (type Cat) as type Animal in array or slice literal: Cat does not implement Animal (Speak method has pointer receiver)
該錯誤的意思是:你嘗試將 Cat
轉爲 Animal
,可是隻有 *Cat
類型實現了該接口。你能夠經過傳入一個指針 (new(Cat)
或者 &Cat{}
)來修復這個錯誤。blog
animals := []Animal{Dog{}, new(Cat), Llama{}, JavaProgrammer{}}
讓咱們作一些相反的事情:咱們傳入一個 *Dog
指針,可是不改變 Dog
的 Speak()
方法:接口
animals := []Animal{new(Dog), new(Cat), Llama{}, JavaProgrammer{}}
這種方式能夠正常工做,由於一個指針類型能夠經過其相關的值類型來訪問值類型的方法,可是反過來不行。即,一個 *Dog
類型的值可使用定義在 Dog
類型上的 Speak()
方法,而 Cat
類型的值不能訪問定義在 *Cat
類型上的方法。
這可能聽起來很神祕,但當你記住如下內容時就清楚了:Go 中的全部東西都是按值傳遞的。每次調用函數時,傳入的數據都會被複制。對於具備值接收者的方法,在調用該方法時將複製該值。例以下面的方法:
func (t T)MyMethod(s string) { // ... }
是 func(T, string)
類型的方法。方法接收器像其餘參數同樣經過值傳遞給函數。
*Cat
的方法不能被
Cat
類型的值調用了。任何一個
Cat
類型的值可能會有不少
*Cat
類型的指針指向它,若是咱們嘗試經過
Cat
類型的值來調用
*Cat
的方法,根本就不知道對應的是哪一個指針。相反,若是
Dog
類型上有一個方法,經過
*Dog
來調用這個方法能夠確切的找到該指針對應的
Gog
類型的值,從而調用上面的方法。運行時,Go 會自動幫咱們作這些,因此咱們不須要像 C語言中那樣使用相似以下的語句
d->Speak()
。
我但願讀完此文後你能夠更加駕輕就熟地使用 Go 中的接口,記住下面這些結論:
interface{}
的值不是任意類型,而是 interface{}
類型(type, value)
interface{}
做爲參數,但最好不要返回 interface{}
*
操做符