2016-08-23 95 views
16

在Golang中,我們使用結構與接收方法。一切都完美到這裏。
但是我不確定接口是什麼。我們在結構體中定義方法,如果我們想在一個結構體上實現一個方法,我們仍然在另一個結構體中再次寫它。
這意味着接口似乎只是方法定義,只是在頁面上佔用額外的不必要空間。爲什麼Golang需要接口?

有沒有任何例子解釋爲什麼我需要一個接口?

+0

你將如何解構未知結構JSON?或者如果fmt.Printf不在那裏,它將如何工作? – YOU

+0

好吧,它的工作如果不在那裏,我猜你是什麼意思如何工作?它是從fmt – nikoss

+0

出口可能重複的[轉:界面的含義是什麼?](http://stackoverflow.com/questions/23148812/go-whats-the-meaning-of-interface) – molivier

回答

30

接口過於龐大,無法給出全面深入的答案,但有些事情要明確說明。

接口是工具。不管你是否使用它們都取決於你,但它們可以使代碼更清晰,並且它們可以在軟件包或客戶端(用戶)和服務器(提供者)之間提供良好的API。

是的,你可以創建自己的struct類型,你可以在「附加」的方法給它,例如:

type Cat struct{} 

func (c Cat) Say() string { return "meow" } 

type Dog struct{} 

func (d Dog) Say() string { return "woof" } 

func main() { 
    c := Cat{} 
    fmt.Println("Cat says:", c.Say()) 
    d := Dog{} 
    fmt.Println("Dog says:", d.Say()) 
} 

我們已經可以看到上面的代碼中的一些重複:使得無論Cat何時Dog說些什麼。我們是否可以將兩者當作同一種實體處理,如動物?不是真的。當然,我們可以將兩者都作爲interface{}來處理,但如果我們這樣做了,我們就不能調用它們的Say()方法,因爲interface{}類型的值沒有定義任何方法。

在上述兩種類型中都有一些相似:兩種方法都具有相同簽名(參數和結果類型)的方法Say()。我們可以捕捉這與接口:

type Sayer interface { 
    Say() string 
} 

接口只包含簽名的方法,但不是他們的實現

請注意,如果其中一個類型爲實現了一個接口,如果它的方法集是接口的超集。沒有宣言的意圖。這是什麼意思?我們以前的CatDog類型已經實現了這個接口,即使這個接口定義在我們之前寫的時候並不存在,我們也沒有觸及它們來標記它們或者什麼。他們只是做。

接口指定行爲。實現接口的類型意味着該類型具有接口「規定」的所有方法。

由於兩者都執行Sayer,我們可以將兩者都作爲Sayer的值處理,它們有這個共同點。看看我們如何能夠團結同時處理:

animals := []Sayer{c, d} 
for _, a := range animals { 
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say()) 
} 

(反映部分是隻拿到類型名,沒有太大的它的現在。)

的重要組成部分,是我們可以將CatDog作爲相同種類(接口類型)處理,並且使用它們/使用它們。如果你是迅速,以利用Say()方法創建其他類型的,他們可以排隊CatDog旁邊:

type Horse struct{} 

func (h Horse) Say() string { return "neigh" } 

animals = append(animals, Horse{}) 
for _, a := range animals { 
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say()) 
} 

比方說,你要編寫這些類型的作品其他代碼。一個輔助函數:

func MakeCatTalk(c Cat) { 
    fmt.Println("Cat says:", c.Say()) 
} 

是,上述功能可與Cat,並沒有別的。如果你想要類似的東西,你必須爲每種類型編寫它。不用說這有多糟糕。

是的,你可以寫它採取的interface{}參數,並使用type assertiontype switches,這將減少的輔助功能的數量,但仍然看起來非常難看。

解決方案?是的,接口。簡單地聲明函數把它定義你想用它做的行爲的接口類型的值,而這一切:

func MakeTalk(s Sayer) { 
    fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say()) 
} 

你可以調用這個函數的Cat值,DogHorse或任何其他類型不知道「,直到現在,它有一個Say()方法。涼。

Go Playground上嘗試使用這些示例。

+0

我還是不明白爲什麼不把動物嵌入馬或貓或狗仍然這沒有意義 – nikoss

+0

@nikoss不知道我明白你的意思,請顯示你如何將它們添加到一個切片,並重復並調用'說()',例如。 – icza

+0

啊這是結合不同類型的結構的點現在我得到它雖然仍然它的有點複雜不能清楚在我的腦海 – nikoss

2

,我將在這裏展示,接口兩個有趣的用例轉到:

1-看到這兩個簡單的接口:

type Reader interface { 
    Read(p []byte) (n int, err error) 
} 

type Writer interface { 
    Write(p []byte) (n int, err error) 
} 

使用這兩個簡單的接口,你可以做這個有趣的魔術:

package main 

import (
    "bufio" 
    "bytes" 
    "fmt" 
    "io" 
    "os" 
    "strings" 
) 

func main() { 
    file, err := os.Create("log.txt") 
    if err != nil { 
     panic(err) 
    } 
    defer file.Close() 

    w := io.MultiWriter(file, os.Stdout) 
    r := strings.NewReader("You'll see this string twice!!\n") 
    io.Copy(w, r) 

    slice := []byte{33, 34, 35, 36, 37, 38, 39, 10, 13} 
    io.Copy(w, bytes.NewReader(slice)) // !"#$%&' 

    buf := &bytes.Buffer{} 
    io.Copy(buf, bytes.NewReader(slice)) 
    fmt.Println(buf.Bytes()) // [33 34 35 36 37 38 39 10 13] 

    _, err = file.Seek(0, 0) 
    if err != nil { 
     panic(err) 
    } 

    r = strings.NewReader("Hello\nWorld\nThis\nis\nVery\nnice\nInterfacing.\n") 
    rdr := io.MultiReader(r, file) 
    scanner := bufio.NewScanner(rdr) 
    for scanner.Scan() { 
     fmt.Println(scanner.Text()) 
    } 
} 

輸出:

You'll see this string twice!! 
!"#$%&' 

[33 34 35 36 37 38 39 10 13] 
Hello 
World 
This 
is 
Very 
nice 
Interfacing. 
You'll see this string twice!! 
!"#$%&' 

我希望這個代碼是非常明顯的:
使用strings.NewReader串讀取和使用io.MultiWriterio.Copy(w, r)同時寫入兩個fileos.Stdout。然後使用bytes.NewReader(slice)從片中讀取並同時寫入fileos.Stdout。然後將切片複製到緩衝區io.Copy(buf, bytes.NewReader(slice)),然後使用file.Seek(0, 0)轉到文件原點,然後首先使用strings.NewReader從字符串讀取,然後繼續使用io.MultiReader(r, file)bufio.NewScanner讀取file,然後使用fmt.Println(scanner.Text())打印全部。


2 - 這是另一個有趣的使用界面:

package main 

import "fmt" 

func main() { 
    i := show() 
    fmt.Println(i) // 0 

    i = show(1, 2, "AB", 'c', 'd', []int{1, 2, 3}, [...]int{1, 2}) 
    fmt.Println(i) // 7 

} 
func show(a ...interface{}) (count int) { 
    for _, b := range a { 
     if v, ok := b.(int); ok { 
      fmt.Println("int: ", v) 
     } 
    } 
    return len(a) 
} 

輸出:

0 
int: 1 
int: 2 
7 

而且很好的例子看看:Explain Type Assertions in Go

另請參閱:Go: What's the meaning of interface{}?

+0

@nikoss我希望這可以幫助。 – 2016-08-23 10:43:02

1

接口提供了一些種類的泛型。想想鴨子打字。

type Reader interface{ 
    Read() 
} 

func callRead(r Reader){ 
     r.Read() 
} 

type A struct{ 
} 
func(_ A)Read(){ 
} 

type B struct{ 
} 
func(_ B)Read(){ 
} 

這是確定通過結構A,並BcallRead,因爲都實現讀卡器接口。 但是如果沒有接口,我們應該爲AB寫兩個函數。

func callRead(a A){ 
    a.Read() 
} 

func callRead2(b B){ 
    b.Read() 
} 
相關問題