1. 程式人生 > 其它 >Golang語言社群--理解 go interface 的 5 個關鍵點

Golang語言社群--理解 go interface 的 5 個關鍵點

大家好,我是社群主編彬哥,本篇文章是給大家轉載的關於Go語言中interface相關的。

1、interface 是一種型別

type I interface {
    Get() int
}

首先 interface 是一種型別,從它的定義可以看出來用了 type 關鍵字,更準確的說 interface 是一種具有一組方法的型別,這些方法定義了 interface 的行為。

go 允許不帶任何方法的 interface ,這種型別的 interface 叫 empty interface

如果一個型別實現了一個 interface 中所有方法,我們說型別實現了該 interface

,所以所有型別都實現了 empty interface,因為任何一種型別至少實現了 0 個方法。go 沒有顯式的關鍵字用來實現 interface,只需要實現 interface 包含的方法即可。

2、interface 變數儲存的是實現者的值

//1
type I interface {    
    Get() int
    Set(int)
}
//2
type S struct {
    Age int
}
func(s S) Get()int {
    return s.Age
}
func(s *S) Set(age int) {
    s.Age = age
}
//3
func f(i I){
    i.Set(10)
    fmt.Println(i.Get())
}
func main() {
    s := S{} 
    f(&s)  //4
}

這段程式碼在 #1 定義了 interface I,在 #2 用 struct S 實現了 I 定義的兩個方法,接著在 #3 定義了一個函式 f 引數型別是 I,S 實現了 I 的兩個方法就說 S 是 I 的實現者,執行 f(&s) 就完了一次 interface 型別的使用。

interface 的重要用途就體現在函式 f 的引數中,如果有多種型別實現了某個 interface,這些型別的值都可以直接使用 interface 的變數儲存

s := S{}
var i I //宣告 i 
i = &s //賦值 s 到 i
fmt.Println(i.Get())

不難看出 interface 的變數中儲存的是實現了 interface 的型別的物件值,這種能力是

duck typing。在使用 interface 時不需要顯式在 struct 上宣告要實現哪個 interface ,只需要實現對應 interface 中的方法即可,go 會自動進行 interface 的檢查,並在執行時執行從其他型別到 interface 的自動轉換,即使實現了多個 interface,go 也會在使用對應 interface 時實現自動轉換,這就是 interface 的魔力所在。

3、如何判斷 interface 變數儲存的是哪種型別

一個 interface 被多種型別實現時,有時候我們需要區分 interface 的變數究竟儲存哪種型別的值,go 可以使用 comma, ok 的形式做區分 value, ok := em.(T):em 是 interface 型別的變數,T代表要斷言的型別,value 是 interface 變數儲存的值,ok 是 bool 型別表示是否為該斷言的型別 T

if t, ok := i.(*S); ok {
    fmt.Println("s implements I", t)
}

ok 是 true 表明 i 儲存的是 *S 型別的值,false 則不是,這種區分能力叫 Type assertions (型別斷言)。

如果需要區分多種型別,可以使用 switch 斷言,更簡單直接,這種斷言方式只能在 switch 語句中使用。

switch t := i.(type) {
case *S:
    fmt.Println("i store *S", t)
case *R:
    fmt.Println("i store *R", t)
}

4、空的 interface

interface{} 是一個空的 interface 型別,根據前文的定義:一個型別如果實現了一個 interface 的所有方法就說該型別實現了這個 interface,空的 interface 沒有方法,所以可以認為所有的型別都實現了 interface{}。如果定義一個函式引數是 interface{} 型別,這個函式應該可以接受任何型別作為它的引數。

func doSomething(v interface{}){    
}

如果函式的引數 v 可以接受任何型別,那麼函式被呼叫時在函式內部 v 是不是表示的是任何型別?並不是,雖然函式的引數可以接受任何型別,並不表示 v 就是任何型別,在函式 doSomething 內部 v 僅僅是一個 interface 型別,之所以函式可以接受任何型別是在 go 執行時傳遞到函式的任何型別都被自動轉換成 interface{}。go 是如何進行轉換的,以及 v 儲存的值究竟是怎麼做到可以接受任何型別的,感興趣的可以看看 Russ Cox 關於 interface 的實現

既然空的 interface 可以接受任何型別的引數,那麼一個 interface{}型別的 slice 是不是就可以接受任何型別的 slice ?

func printAll(vals []interface{}) { //1
        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 錯誤,why?

這個錯誤說明 go 沒有幫助我們自動把 slice 轉換成 interface{} 型別的 slice,所以出錯了。go 不會對 型別是interface{} 的 slice 進行轉換 。為什麼 go 不幫我們自動轉換,一開始我也很好奇,最後終於在 go 的 wiki 中找到了答案 https://github.com/golang/go/wiki/InterfaceSlice 大意是 interface{} 會佔用兩個字長的儲存空間,一個是自身的 methods 資料,一個是指向其儲存值的指標,也就是 interface 變數儲存的值,因而 slice []interface{} 其長度是固定的N*2,但是 []T 的長度是N*sizeof(T),兩種 slice 實際儲存值的大小是有區別的(文中只介紹兩種 slice 的不同,至於為什麼不能轉換猜測可能是 runtime 轉換代價比較大)。

但是我們可以手動進行轉換來達到我們的目的。

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
        interfaceSlice[i] = d
}

5、interface 的實現者的 receiver 如何選擇

在我們上文的例子中呼叫 f 是 f(&s) 也就是 S 的指標型別,為什麼不能是 f(s) 呢,如果是 s 會有什麼問題?改成 f(s) 然後執行程式碼。

cannot use s (type S) as type I in argument to f:
        S does not implement I (Set method has pointer receiver)

這個錯誤的意思是 S 沒有實現 I,哪裡出了問題?關鍵點是 S 中 set 方法的 receiver 是個 pointer *S

interface 定義時並沒有嚴格規定實現者的方法 receiver 是個 value receiver 還是 pointer receiver,上面程式碼中的 S 的 Set receiver 是 pointer,也就是實現 I 的兩個方法的 receiver 一個是 value 一個是 pointer,使用 f(s)的形勢呼叫,傳遞給 f 的是個 s 的一份拷貝,在進行 s 的拷貝到 I 的轉換時,s 的拷貝不滿足 Set 方法的 receiver 是個 pointer,也就沒有實現 I。go 中函式都是按值傳遞即 passed by value

那反過來會怎樣,如果 receiver 是 value,函式用 pointer 的形式呼叫?

type I interface {
        Get() int
        Set(int)
}
type SS struct {
        Age int
}
func (s SS) Get() int {
        return s.Age
}
func (s SS) Set(age int) {
        s.Age = age
}
func f(i I) {
        i.Set(10)
        fmt.Println(i.Get())
}
func main(){
          ss := SS{}
        f(&ss) //ponter
        f(ss)  //value
}

I 的實現者 SS 的方法 receiver 都是 value receiver,執行程式碼可以看到無論是 pointer 還是 value 都可以正確執行。

導致這一現象的原因是什麼?

如果是按 pointer 呼叫,go 會自動進行轉換,因為有了指標總是能得到指標指向的值是什麼,如果是 value 呼叫,go 將無從得知 value 的原始值是什麼,因為 value 是份拷貝。go 會把指標進行隱式轉換得到 value,但反過來則不行

對於 receiver 是 value 的 method,任何在 method 內部對 value 做出的改變都不影響呼叫者看到的 value,這就是按值傳遞。

另一個說明上述現象的例子是這樣的來自 https://play.golang.org/p/TvR758rfre

package main
import (
        "fmt"
)
type Animal interface {
        Speak() string
}
type Dog struct {
}
func (d Dog) Speak() string {
        return "Woof!"
}
type Cat struct {
}
//1
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!"
}
func main() {
        animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
        for _, animal := range animals {
                fmt.Println(animal.Speak())
        }
}

1Cat 的 speak receiver 是 pointer,interface Animal 的 slice,Cat 的值是一個 value,同樣會因為 receiver 不一致而導致無法執行。