棒棒噠的Go Interface
原文:Interface: the awesomesause of Go
譯文出處:醬油蔡的醬油壇
讓我們稍微重溫下。我們知道基本的數據類型,使用它們,並且學會創建複合數據類型。我們還知道一些基本的流程控制結構。所以我們能基於這些編程實現功能。
接下來我們發現函數實際上也是一種數據,它們是它們自己的值並且也有類型。我們繼續學習方法。我們使用方法寫函數來操作數據,然後繼續實現功能性的數據類型。
這一章我們將進入Go的下一個境界。我們將學習對象的一些天生的洪荒之力來做一些實際事情。
廢話少說,直奔主題!
Interface是什麼?
簡而言之,Interface就是一組方法的集合。我們使用Interface指定一個給定對象的行爲。例如前面章節提到的,Student
和Employee
都有SayHi
,但是是不同的實現。這都不是事兒,它們都知道怎麼說”Hi”.
加點推理:Student
和Employee
實現了另外的方法Sing
,同時Employee
實現了方法SpendSalary
, Student
實現了方法BorrowMoney
。總結下就是:Student
實現了方法:SayHi
, Sing
, BorrowMoney
;Employee
實現了方法SayHi
, Sing
和SpendSalary
。
這些方法的集合就是Student
和Employee
滿足的Interface.例如:Student
和Employee
都符合包含方法SayHi
和Sing
的Interface。但是Employee
不符合由方法Sing
,SayHi
和BorrowMoney
組成的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可以有任意的對象實現。上面示例中,Student
和Employee
都實現了Men
Interface。
反之,一個對象也可以實現任意的Interface。上面示例中,Student
就實現了Men
和YoungChap
Interface。同時Employee
實現了Men
和ElderlyGent
。
最後,所有類型都實現了empty Interface,你應該可以猜到,就是沒有方法的Interface,使用interface{}
表示。
這個時候你可能會說“牛逼,但是定義Interface的關鍵是什麼呢?難道僅僅是描述類型的行爲,他們的共同點是什麼呢?”
稍等!這裏還有點東西——當然這裏肯定還有更多的貨。
另外,我剛纔有提到empty interface還沒有方法嗎?
Interface的值
由於Interface是它自身的類型,你可能還想知道一個interface類型的值到底是啥。
Tata!這裏有個驚天好消息:如果你聲明瞭interface 變量,它可以存儲任何實現了這個interface的數據類型。
就是說,如果你定義了一個Men
類型的interface m
,它可以存儲Student
,Employee
或… …。這是因爲它們都實現了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
是怎麼做到的,沒有提到Student
和Employee
類型。關於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.Print
, fmt.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.Stringer
Interface類型,因此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 package
。sort 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!!!