Go語言interface詳解

interfaceapp

Go語言裏面設計最精妙的應該算interface,它讓面向對象,內容組織實現很是的方便,當你看完這一章,你就會被interface的巧妙設計所折服。函數

什麼是interface學習

簡單的說,interface是一組method的組合,咱們經過interface來定義對象的一組行爲。測試

咱們前面一章最後一個例子中Student和Employee都能SayHi,雖然他們的內部實現不同,可是那不重要,重要的是他們都能say hispa

讓咱們來繼續作更多的擴展,Student和Employee實現另外一個方法Sing,而後Student實現方法BorrowMoney而Employee實現SpendSalary。設計

這樣Student實現了三個方法:SayHi、Sing、BorrowMoney;而Employee實現了SayHi、Sing、SpendSalary。指針

上面這些方法的組合稱爲interface(被對象Student和Employee實現)。例如Student和Employee都實現了interface:SayHi和Sing,也就是這兩個對象是該interface類型。而Employee沒有實現這個interface:SayHi、Sing和BorrowMoney,由於Employee沒有實現BorrowMoney這個方法。code

interface類型對象

interface類型定義了一組方法,若是某個對象實現了某個接口的全部方法,則此對象就實現了此接口。詳細的語法參考下面這個例子接口

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段Human
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段Human
    company string
    money float32
}

//Human對象實現Sayhi方法
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human對象實現Sing方法
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

//Human對象實現Guzzle方法
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee重載Human的Sayhi方法
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //此句能夠分紅多行
}

//Student實現BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
    s.loan += amount // (again and again and...)
}

//Employee實現SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// 定義interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

經過上面的代碼咱們能夠知道,interface能夠被任意的對象實現。咱們看到上面的Men interface被Human、Student和Employee實現。同理,一個對象能夠實現任意多個interface,例如上面的Student實現了Men和YoungChap兩個interface。

最後,任意的類型都實現了空interface(咱們這樣定義:interface{}),也就是包含0個method的interface。

interface值

那麼interface裏面到底能存什麼值呢?若是咱們定義了一個interface的變量,那麼這個變量裏面能夠存實現這個interface的任意類型的對象。例如上面例子中,咱們定義了一個Men interface類型的變量m,那麼m裏面能夠存Human、Student或者Employee值。

由於m可以持有這三種類型的對象,因此咱們能夠定義一個包含Men類型元素的slice,這個slice能夠被賦予實現了Men接口的任意結構的對象,這個和咱們傳統意義上面的slice有所不一樣。

讓咱們來看一下下面這個例子:

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段
    company string
    money float32
}

//Human實現SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human實現Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee重載Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
    }

// Interface Men被Human,Student和Employee實現
// 由於這三個類型都實現了這兩個方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

    //定義Men類型的變量i
    var i Men

    //i能存儲Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能存儲Employee
    i = Tom
    fmt.Println("This is Tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定義了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //這三個都是不一樣類型的元素,可是他們實現了interface同一個接口
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x{
        value.SayHi()
    }
}

經過上面的代碼,你會發現interface就是一組抽象方法的集合,它必須由其餘非interface類型實現,而不能自我實現, Go經過interface實現了duck-typing:即"當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子"。

空interface

空interface(interface{})不包含任何的method,正由於如此,全部的類型都實現了空interface。空interface對於描述起不到任何的做用(由於它不包含任何的method),可是空interface在咱們須要存儲任意類型的數值的時候至關有用,由於它能夠存儲任意類型的數值。它有點相似於C語言的void*類型。

// 定義a爲空接口
var a interface{}
var i int = 5
s := "Hello world"
// a能夠存儲任意類型的數值
a = i
a = s

一個函數把interface{}做爲參數,那麼他能夠接受任意類型的值做爲參數,若是一個函數返回interface{},那麼也就能夠返回任意類型的值。是否是頗有用啊!

interface函數參數

interface的變量能夠持有任意實現該interface類型的對象,這給咱們編寫函數(包括method)提供了一些額外的思考,咱們是否是能夠經過定義interface參數,讓函數接受各類類型的參數。

舉個例子:fmt.Println是咱們經常使用的一個函數,可是你是否注意到它能夠接受任意類型的數據。打開fmt的源碼文件,你會看到這樣一個定義:

type Stringer interface {
     String() string
}

也就是說,任何實現了String方法的類型都能做爲參數被fmt.Println調用,讓咱們來試一試

package main
import (
    "fmt"
    "strconv"
)

type Human struct {
    name string
    age int
    phone string
}

// 經過這個方法 Human 實現了 fmt.Stringer
func (h Human) String() string {
    return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", 39, "000-7777-XXX"}
    fmt.Println("This Human is : ", Bob)
}

如今咱們再回顧一下前面的Box示例,你會發現Color結構也定義了一個method:String。其實這也是實現了fmt.Stringer這個interface,即若是須要某個類型能被fmt包以特殊的格式輸出,你就必須實現Stringer這個接口。若是沒有實現這個接口,fmt將以默認的方式輸出。

//實現一樣的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

注:實現了error接口的對象(即實現了Error() string的對象),使用fmt輸出時,會調用Error()方法,所以沒必要再定義String()方法了。

interface變量存儲的類型

咱們知道interface的變量裏面能夠存儲任意類型的數值(該類型實現了interface)。那麼咱們怎麼反向知道這個變量裏面實際保存了的是哪一個類型的對象呢?目前經常使用的有兩種方法:

Comma-ok斷言

Go語言裏面有一個語法,能夠直接判斷是不是該類型的變量: value, ok = element.(T),這裏value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型。

若是element裏面確實存儲了T類型的數值,那麼ok返回true,不然返回false。

讓咱們經過一個例子來更加深刻的理解。

 

package main

import (
    "fmt"
    "strconv"
)

type Element interface{}
type List [] Element

type Person struct {
    name string
    age int
}

//定義了String方法,實現了fmt.Stringer
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

func main() {
    list := make(List, 3)
    list[0] = 1 // an int
    list[1] = "Hello" // a string
    list[2] = Person{"Dennis", 70}

    for index, element := range list {
        if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
        } else {
            fmt.Println("list[%d] is of a different type", index)
        }
    }
}

是否是很簡單啊,同時你是否注意到了多個if裏面,還記得我前面介紹流程時講過,if裏面容許初始化變量。

也許你注意到了,咱們斷言的類型越多,那麼if else也就越多,因此才引出了下面要介紹的switch。

switch測試

最好的講解就是代碼例子,如今讓咱們重寫上面的這個實現

package main

import (
    "fmt"
    "strconv"
)

type Element interface{}
type List [] Element

type Person struct {
    name string
    age int
}

//打印
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

func main() {
    list := make(List, 3)
    list[0] = 1 //an int
    list[1] = "Hello" //a string
    list[2] = Person{"Dennis", 70}

    for index, element := range list{
        switch value := element.(type) {
            case int:
                fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
            case string:
                fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
            case Person:
                fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
            default:
                fmt.Println("list[%d] is of a different type", index)
        }
    }
}

這裏有一點須要強調的是:element.(type)語法不能在switch外的任何邏輯裏面使用,若是你要在switch外面判斷一個類型就使用comma-ok。

嵌入interface

Go裏面真正吸引人的是它內置的邏輯語法,就像咱們在學習Struct時學習的匿名字段,多麼的優雅啊,那麼相同的邏輯引入到interface裏面,那不是更加完美了。若是一個interface1做爲interface2的一個嵌入字段,那麼interface2隱式的包含了interface1裏面的method。

咱們能夠看到源碼包container/heap裏面有這樣的一個定義

type Interface interface {
    sort.Interface //嵌入字段sort.Interface
    Push(x interface{}) //a Push method to push elements into the heap
    Pop() interface{} //a Pop elements that pops elements from the heap
}

咱們看到sort.Interface其實就是嵌入字段,把sort.Interface的全部method給隱式的包含進來了。也就是下面三個方法:

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less returns whether the element with index i should sort
    // before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

另外一個例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer兩個interface:

// io.ReadWriter
type ReadWriter interface {
    Reader
    Writer
}

反射

Go語言實現了反射,所謂反射就是能檢查程序在運行時的狀態。咱們通常用到的包是reflect包。如何運用reflect包,官方的這篇文章詳細的講解了reflect包的實現原理,laws of reflection

使用reflect通常分紅三步,下面簡要的講解一下:要去反射是一個類型的值(這些值都實現了空interface),首先須要把它轉化成reflect對象(reflect.Type或者reflect.Value,根據不一樣的狀況調用不一樣的函數)。這兩種獲取方式以下:

t := reflect.TypeOf(i)    //獲得類型的元數據,經過t咱們能獲取類型定義裏面的全部元素
v := reflect.ValueOf(i)   //獲得實際的值,經過v咱們獲取存儲在裏面的值,還能夠去改變值

轉化爲reflect對象以後咱們就能夠進行一些操做了,也就是將reflect對象轉化成相應的值,例如

tag := t.Elem().Field(0).Tag  //獲取定義在struct裏面的標籤
name := v.Elem().Field(0).String()  //獲取存儲在第一個字段裏面的值

獲取反射值能返回相應的類型和數值

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

最後,反射的話,那麼反射的字段必須是可修改的,咱們前面學習過傳值和傳引用,這個裏面也是同樣的道理。反射的字段必須是可讀寫的意思是,若是下面這樣寫,那麼會發生錯誤

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

若是要修改相應的值,必須這樣寫

var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

valueOf 得到的是複製,對於它的修改對原來的數據是沒有影響的。若是想要修改原值,就要用指針。再用p.Elem就能得到能夠修改的對象了。

要處理struct的話,

也要像這樣

type T struct {
	A int
	B string
}

func main() {
	a := 1
	fmt.Println(reflect.TypeOf(a))
	fmt.Println(reflect.ValueOf(a))
	t := T{23, "skidoo"}
	fmt.Println(reflect.TypeOf(t))
	fmt.Println(reflect.ValueOf(t))

	s := reflect.ValueOf(&t).Elem() //ValueOf.Elem必需要指針類型,不然會panic
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		fmt.Printf("%d: %s %s = %v\n", i,
			typeOfT.Field(i).Name, f.Type(), f.Interface())
	}
}

輸出以下:

int
1

main.T
{23 skidoo}
0: A int = 23
1: B string = skidoo

經過Elem的得到可修改對象,而後再Filed獲取對應的value。

相關文章
相關標籤/搜索