棒棒噠的Go Interface

棒棒噠的Go Interface

原文:Interface: the awesomesause of Go

譯文出處:醬油蔡的醬油壇

讓我們稍微重溫下。我們知道基本的數據類型,使用它們,並且學會創建複合數據類型。我們還知道一些基本的流程控制結構。所以我們能基於這些編程實現功能。

接下來我們發現函數實際上也是一種數據,它們是它們自己的值並且也有類型。我們繼續學習方法。我們使用方法寫函數來操作數據,然後繼續實現功能性的數據類型。

這一章我們將進入Go的下一個境界。我們將學習對象的一些天生的洪荒之力來做一些實際事情。

廢話少說,直奔主題!

Interface是什麼?

簡而言之,Interface就是一組方法的集合。我們使用Interface指定一個給定對象的行爲。例如前面章節提到的,StudentEmployee都有SayHi,但是是不同的實現。這都不是事兒,它們都知道怎麼說”Hi”.

加點推理:StudentEmployee實現了另外的方法Sing,同時Employee實現了方法SpendSalary, Student實現了方法BorrowMoney。總結下就是:Student實現了方法:SayHi, Sing, BorrowMoneyEmployee實現了方法SayHi, SingSpendSalary

這些方法的集合就是StudentEmployee滿足的Interface.例如:StudentEmployee都符合包含方法SayHiSing的Interface。但是Employee不符合由方法Sing,SayHiBorrowMoney組成的Interface,因爲Employee沒有實現方法BorrowMoney

Interface類型

Interface類型指定了一組由其他對象實現的方法。使用如下語法:

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //an anonymous field of type Human
    school string
    loan float32
}

type Employee struct {
    Human //an anonymous field of type Human
    company string
    money float32
}

// A human likes to stay... err... *say* hi
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// A human can sing a song, preferrably to a familiar tune!
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

// A Human man likes to guzzle his beer!
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee's method for saying hi overrides a normal Human's one
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) //Yes you can split into 2 lines here.
}

// A Student borrows some money
func (s *Student) BorrowMoney(amount float32) {
    loan += amount // (again and again and...)
}

// An Employee spends some of his salary
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// INTERFACES
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可以有任意的對象實現。上面示例中,StudentEmployee都實現了Men Interface。

反之,一個對象也可以實現任意的Interface。上面示例中,Student就實現了MenYoungChap Interface。同時Employee實現了MenElderlyGent

最後,所有類型都實現了empty Interface,你應該可以猜到,就是沒有方法的Interface,使用interface{}表示。

這個時候你可能會說“牛逼,但是定義Interface的關鍵是什麼呢?難道僅僅是描述類型的行爲,他們的共同點是什麼呢?”

稍等!這裏還有點東西——當然這裏肯定還有更多的貨。

另外,我剛纔有提到empty interface還沒有方法嗎?

Interface的值

由於Interface是它自身的類型,你可能還想知道一個interface類型的值到底是啥。

Tata!這裏有個驚天好消息:如果你聲明瞭interface 變量,它可以存儲任何實現了這個interface的數據類型。

就是說,如果你定義了一個Men類型的interface m,它可以存儲StudentEmployee或… …。這是因爲它們都實現了Men interface指定的方法。

同我一起總結下:如果m可以存儲任何不同類型的值,我們可以很輕鬆的聲明一個Men類型的Slice存儲各種不同的值。這就不再是傳統的經典Slice類型了。

來一個示例梳理一下剛剛我介紹的東西:

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //an anonymous field of type Human
    school string
    loan float32
}

type Employee struct {
    Human //an anonymous field of type Human
    company string
    money float32
}

//A human method to say hi
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//A human can sing a song
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee's method overrides Human's one
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) //Yes you can split into 2 lines here.
}

// 爬蟲兇猛,譯文出處:[醬油蔡的醬油壇](http://blog.csdn.net/xingwangc2014)
// Interface Men is implemented by Human, Student and Employee
// because it contains methods implemented by them.
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{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}

    //a variable of the interface type Men
    var i Men

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

    //i can store an Employee too
    i = Tom
    fmt.Println("This is Tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //a slice of Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //These elements are of different types that satisfy the Men interface
    x[0], x[1], x[2] = paul, sam, mike

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

輸出:

This is Mike, a Student:
Hi, I am Mike you can call me on 222-222-XXX
La la la la... November rain
This is Tom, an Employee:
Hi, I am Sam, I work at Things Ltd.. Call me on 444-222-XXX
La la la la... Born to be wild
Let’s use a slice of Men and see what happens
Hi, I am Paul you can call me on 111-222-XXX
Hi, I am Sam, I work at Golang Inc.. Call me on 444-222-XXX
Hi, I am Mike you can call me on 222-222-XXX

你可能已經注意到了:Interface類型是抽象的,它們自身並不實現指定的、精確的數據結構或方法。它們只是簡單的描述:”如果有對象能夠做到這些,它們就可以在這裏被使用”。

注:這些類型都沒有提及任何的interface,它們的實現不會明確的提到某個具體的interface。類似的,一個interface也不會指定或在乎哪個類型需要實現它。看看Men是怎麼做到的,沒有提到StudentEmployee類型。關於Interface的事實就是,如果一個類型實現了它所聲明的接口,則它可以被用於應用該類型的值。

Empty Interface

Empty Interface interface{}一個方法也不包含,所以任何類型都實現了它所有的0個方法。

Empty Interface對於描述一個行爲並沒有什麼用(顯然,它是一個只有很少字符的實體)。但是當我們想要存儲任何類型的值時,它就顯得非常有用了(因爲所有類型都實現了empty interface)。

// a is an empty interface variable
var a interface{}
var i int = 5
s := "Hello world"
// These are legal statements
a = i
a = s

如果一個函數的傳參是interface{}類型,則它可以接受任何類型的參數。同理,如果一個函數返回一個interface{},我們希望它能返回任何類型的數據。

這個到底多有用?繼續往下讀,你就會發現。

使用Interface傳參的函數

前面的示例顯示interface變量能夠存儲實現它的任何類型的任何值,這給了我們一些如果針對不同的數據(不同的類型)構建容器。順着這個思路,我們可以考慮接受Interface傳參的函數或方法以便該函數或方法能夠處理實現接口的任何類型。

例如:現在你已經知道fmt.Print是一個可變參數函數,對吧?它可以接受任何數量的實參。但是你注意到了,有時候我們使用的是string,有時候是int,有時候又是float等類型的參數了嗎?事實上,如果你看過fmt package的源碼、文檔,你將發現有一個叫Stringer的Interface被定義了。

//The Stringer interface found in fmt package
type Stringer interface {
     String() string
}

任何一個實現了Stringer接口的類型都可以傳遞給函數fmt.Printfmt.Print將打印該類型的String方法返回的string。

讓我們來試一下:

package main
import (
    "fmt"
    "strconv" //for conversions to and from string
)

type Human struct {
    name string
    age int
    phone string
}

//Returns a nice string representing a Human
//With this method, Human implements fmt.Stringer
func (h Human) String() string {
    //We called strconv.Itoa on h.age to make a string out of it.
    //Also, thank you, UNICODE!
    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)
}

輸出:

This Human is : ❰Bob - 39 years - ✆ 000-7777-XXX❱

擼一遍剛纔我們是怎麼使用fmt.Print的,我們給了它一個Human類型的Bob作爲實參,然後它非常完美的打印出來了。我們所有要做的就是爲Humen類型實現一個簡單的String方法返回一個string。這樣做就意味着Humen類型滿足了fmt.StringerInterface類型,因此fmt.Print能夠接受Humen類型作爲傳參。

還能想起colored boxex example嗎?我們有一個Color類型,實現了一個String方法,讓我們回到那個示例,並替代fmt.Println直接使用這裏的方法打印color,你將看到它如你期望的一樣工作。

//These two lines do the same thing
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

很酷,是不是?
(-爬-蟲兇猛:本文出處:醬油蔡的醬油壇

另一個將你深愛interface的示例是sort packagesort package提供函數對int、float、string以及更多類型的slice進行排序。

先看一個基本的示例,然後我們將讓你看到這個包的魔力。

package main
import(
    "fmt"
    "sort"
)

func main() {
    // list is a slice of ints. It is unsorted as you can see
    list := []int {1, 23, 65, 11, 0, 3, 233, 88, 99}
    fmt.Println("The list is: ", list)

    // let's use Ints function that comes in sort
    // Ints([]int) sorts its parameter in ibcreasing order. Go read its doc.
    sort.Ints(list)
    fmt.Println("The sorted list is: ", list)
}

輸出:

The list is: [1 23 65 11 0 3 233 88 99]
The sorted list is: [0 1 3 11 23 65 88 99 233]

用起來就是這麼簡單。但是我想給你看的遠不止如此。事實上,sort package定義了一個接口,該接口甚至就簡單的叫Interface,它包含三個方法:

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)
}

截取部分sort package文檔:

一個滿足排序的類型,典型的是一個集合。接口可以通過這個包中的程序進行排序。這些方法要求集合中的元素可以被整型的索引枚舉

所以如果我們想排序任何類型數據的slice,只需要爲該類型實現這三個方法。我們繼續在Humen上做實驗,我們期望基於年齡對Humen類型的slice排序。

package main
import (
    "fmt"
    "strconv"
    "sort"
)

type Human struct {
    name string
    age int
    phone string
}

func (h Human) String() string {
    return "(name: " + h.name + " - age: "+strconv.Itoa(h.age)+ " years)"
}

type HumanGroup []Human //HumanGroup is a type of slices that contain Humans

func (g HumanGroup) Len() int {
    return len(g)
}

func (g HumanGroup) Less(i, j int) bool {
    if g[i].age < g[j].age {
        return true
    }
    return false
}

func (g HumanGroup) Swap(i, j int){
    g[i], g[j] = g[j], g[i]
}

func main(){
    group := HumanGroup{
        Human{name:"Bart", age:24},
        Human{name:"Bob", age:23},
        Human{name:"Gertrude", age:104},
        Human{name:"Paul", age:44},
        Human{name:"Sam", age:34},
        Human{name:"Jack", age:54},
        Human{name:"Martha", age:74},
        Human{name:"Leo", age:4},
    }

    //Let's print this group as it is
    fmt.Println("The unsorted group is:")
    for _, v := range group{
        fmt.Println(v)
    }

    //Now let's sort it using the sort.Sort function
    sort.Sort(group)

    //Print the sorted group
    fmt.Println("\nThe sorted group is:")
    for _, v := range group{
        fmt.Println(v)
    }
}

輸出:

The unsorted group is:
(name: Bart - age: 24 years)
(name: Bob - age: 23 years)
(name: Gertrude - age: 104 years)
(name: Paul - age: 44 years)
(name: Sam - age: 34 years)
(name: Jack - age: 54 years)
(name: Martha - age: 74 years)
(name: Leo - age: 4 years)

The sorted group is:
(name: Leo - age: 4 years)
(name: Bob - age: 23 years)
(name: Bart - age: 24 years)
(name: Sam - age: 34 years)
(name: Paul - age: 44 years)
(name: Jack - age: 54 years)
(name: Martha - age: 74 years)
(name: Gertrude - age: 104 years)

成功!就像文檔中所描述的一樣工作。我們並沒有專門針對HumenGroup進行排序,只是實現了幾個sort的方法(len, less,swap)。Sort函數需要那個接口爲我們實現slice的排序。

我知道你肯定很好奇,想知道這個到底是怎麼工作的。其實很簡單,sort package中Sort函數的簽名是這樣的:func Sort(data Interface)。它接受任何實現了sort.Interface接口的類型,然後深入調用你爲你的類型定義的len, less, swap方法。

去看看sort package的源碼吧,你將很容易的看到這些。

我們已經看了函數使用interface傳參的不同示例,這些interface的傳參提供對不同類型的抽象。下面讓我們自己來實踐一下,讓我們腦洞大開自己來寫一個接收interface的函數吧。

我們自己的示例

你是否還記得我們前面看過了Max(s []int) int函數呢?還有Older(s []Person) Person。他們都有相同的功能。事實上檢索int、float slice或一組人中的older,其實都是在幹相同的事情:循環和比較大小。

開工!

package main
import (
    "fmt"
    "strconv"
)

//A basic Person struct
type Person struct {
    name string
    age int
}

//Some slices of ints, floats and Persons
type IntSlice []int
type Float32Slice []float32
type PersonSlice []Person

type MaxInterface interface {
    // Len is the number of elements in the collection.
    Len() int
    //Get returns the element with index i in the collection
    Get(i int) interface{}
    //Bigger returns whether the element at index i is bigger that the j one
    Bigger(i, j int) bool
}

//Len implementation for our three types
func (x IntSlice) Len() int {return len(x)}
func (x Float32Slice) Len() int {return len(x)}
func (x PersonSlice) Len() int {return len(x)}

//Get implementation for our three types
func(x IntSlice) Get(i int) interface{} {return x[i]}
func(x Float32Slice) Get(i int) interface{} {return x[i]}
func(x PersonSlice) Get(i int) interface{} {return x[i]}

//Bigger implementation for our three types
func (x IntSlice) Bigger(i, j int) bool {
    if x[i] > x[j] { //comparing two int
        return true
    }
    return false
}

func (x Float32Slice) Bigger(i, j int) bool {
    if x[i] > x[j] { //comparing two float32
        return true
    }
    return false
}

func (x PersonSlice) Bigger(i, j int) bool {
    if x[i].age > x[j].age { //comparing two Person ages
        return true
    }
    return false
}

//Person implements fmt.Stringer interface
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

/*
 Returns a bool and a value
 - The bool is set to true if there is a MAX in the collection
 - The value is set to the MAX value or nil, if the bool is false
*/
func Max(data MaxInterface) (ok bool, max interface{}) {
    if data.Len() == 0{
        return false, nil //no elements in the collection, no Max value
    }
    if data.Len() == 1{ //Only one element, return it alongside with true
        return true, data.Get(1)
    }
    max = data.Get(0)//the first element is the max for now
    m := 0
    for i:=1; i<data.Len(); i++ {
        if data.Bigger(i, m){ //we found a bigger value in our slice
            max = data.Get(i)
            m = i
        }
    }
    return true, max
}

func main() {
    islice := IntSlice {1, 2, 44, 6, 44, 222}
    fslice := Float32Slice{1.99, 3.14, 24.8}
    group := PersonSlice{
        Person{name:"Bart", age:24},
        Person{name:"Bob", age:23},
        Person{name:"Gertrude", age:104},
        Person{name:"Paul", age:44},
        Person{name:"Sam", age:34},
        Person{name:"Jack", age:54},
        Person{name:"Martha", age:74},
        Person{name:"Leo", age:4},
    }

    //Use Max function with these different collections
    _, m := Max(islice)
    fmt.Println("The biggest integer in islice is :", m)
    _, m = Max(fslice)
    fmt.Println("The biggest float in fslice is :", m)
    _, m = Max(group)
    fmt.Println("The oldest person in the group is:", m)
}

輸出:

The biggest integer in islice is : 222
The biggest float in fslice is : 24.8
The oldest person in the group is: (name: Gertrude - age: 104 years)

MaxInterface interface包含三個必須實現的接口:

  • Len() int:必須返回集合的長度
  • Get(int i) interface{}:返回集合中索引爲i的元素。注意,我們設計它返回的結果爲interface{}類型。這個集合可以是任何類型的集合,並能夠將集合中的值通過空interface返回。
  • Bigger(i, j int) bool:如果集合中索引爲i的值大於索引爲j的值則返回true,否則返回false。

爲什麼是這3個方法?

  • Len() int:因爲沒有任何假設說集合必須是slice。想象一個複雜的數據結構可能有它自己的數據長度定義。
  • Get(int i) interface{}:同樣,沒有人說這是在處理slice集合。複雜的數據結構可能以更加複雜的方式存儲和檢索它們的元素。
  • Bigger(i, j int) bool:比較數字類型是顯而易見的,我們知道那一個更大。而且編程語言都有數值比較的操作符:<,>, =等。但是更大的值這個概念比較微妙。Person A比B更老(大的另一種表達方式),如果A的年紀比B大。

我們的各種類型對這幾個方法的實現也是相當的簡單粗暴。

這段程序的核心是函數Max(data MaxInterface)(ok bool, max interface{}), 它接收實現MaxInterface接口的data,並返回兩個值得結果組合。一個返回時bool類型, true表示有最大值,否則表示collection是空的),另一個空接口用於返回集合的最大值。

注意Max函數是怎麼實現的:沒有提到任何的集合類型,它所用到的所用方法都是源於接口定義。這種抽象正是Max函數能夠對任何實現了MaxInterface接口的類型有效的保證。每次我們針對給定類型的數據調用Max函數,它都調用該類型所實現的方法。相當的有魅力。如果我們需要找出任何集合的最大值,我們只需要爲那個類型實現MaxInterface接口。它就能夠像fmt.Print一樣工作。

這一章就講到這裏,稍作休息然後開始美好的一天。下一次我們將討論一些interface的細節。不用擔心,我保證比這章更簡單。

C’mon, admit it! You’ve got a NERDON after this chapter!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章