本文章翻譯自《Let's learn Go》的“Interfaces: the awesomesauce of Go”一節(jié),原文地址:http://go-book.appspot.com/interfaces.html。
讓我們重新梳理一下以前的內(nèi)容。我們了解了基本的數(shù)據(jù)類型,我們學(xué)習(xí)了如何利用已有的數(shù)據(jù)類型構(gòu)造出一個(gè)新的數(shù)據(jù)類型。我們學(xué)習(xí)了基本的控制流語(yǔ)句,我們將要結(jié)合這些知識(shí)構(gòu)建一些簡(jiǎn)單的應(yīng)用程序。
接下來(lái)我們會(huì)發(fā)現(xiàn)函數(shù)實(shí)際上也是一種數(shù)據(jù),他們具有自己的值和類型。我們將要學(xué)習(xí)關(guān)于方法的基本知識(shí)。我們使用方法來(lái)構(gòu)建作用于數(shù)據(jù)上的函數(shù),從而使某個(gè)數(shù)據(jù)類型完成特定的功能。
在本章中,將要學(xué)習(xí)一個(gè)新的領(lǐng)域。我們將學(xué)習(xí)使用面向?qū)ο缶幊痰撵`魂去構(gòu)建程序,讓我們一起做這件事吧。
What is an interface?
簡(jiǎn)單的說(shuō),接口就是一組方法簽名的集合。我們使用一個(gè)接口來(lái)識(shí)別一個(gè)對(duì)象的能夠進(jìn)行的操作。
舉例來(lái)說(shuō),在以前的章節(jié)中,我們看到Student和Emlpoyee都可以執(zhí)行SayHi函數(shù),但是他們的運(yùn)行結(jié)構(gòu)是不同的,但是這個(gè)是無(wú)關(guān)緊要的,本質(zhì)是他們都可以說(shuō)“Hi”(這部分下邊的內(nèi)容會(huì)有體現(xiàn))。
我們假設(shè)Student和Employee實(shí)現(xiàn)了另外一個(gè)共同的函數(shù)Sing。Employ實(shí)現(xiàn)了SpendSalary函數(shù),與此相對(duì)應(yīng)Student實(shí)現(xiàn)了BorrowMoney函數(shù)。
所以Student實(shí)現(xiàn)了SayHi、Sing和BorrowMoney函數(shù),Employee實(shí)現(xiàn)了SayHi、Sing和SpendSalary函數(shù)。
這些方法的集合就是Student和Employee滿足的接口類型。舉例來(lái)說(shuō),Student和Employee都滿足包含SayHi和Sing方法簽名的接口。但是Employee不能滿足包含SayHi、Sing和BorrowMoney的接口類型,因?yàn)镋mployee沒有實(shí)現(xiàn)方法BorrowMoney。
The interface type
接口類型實(shí)際上是一組方法簽名的清單,我們將遵循下面的接口約束:
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)
}
正如你所看到的那樣,一個(gè)接口可以被任意數(shù)量的類型滿足,在這里Student和Employee都實(shí)現(xiàn)了Men接口。
并且,一個(gè)類型可以實(shí)現(xiàn)任意數(shù)量的接口,在這里,Student實(shí)現(xiàn)了Men和YoungChap接口,Employee實(shí)現(xiàn)了Men和ElderlyGent接口。
最后需要說(shuō)明的是,每個(gè)類型都實(shí)現(xiàn)了一個(gè)空接口interface{},大概你要說(shuō),這意味著該類型沒有方法,我們重新聲明一下interface{}的意義。
你可能自以為發(fā)現(xiàn)接口類型的意義:
非常酷,接口類型的意義就是描述數(shù)據(jù)類型的行為,以及數(shù)據(jù)類型的共性特征
然而事實(shí)上,接口類型的意義遠(yuǎn)遠(yuǎn)不止于此。
順便說(shuō)一下,我說(shuō)過(guò)空接口意味著不包含方法簽名嗎?
Interface values
因?yàn)榻涌谝彩且环N類型,你會(huì)困惑于一個(gè)接口類型的值到底是什么。
有一個(gè)好消息就是:如果你聲明了一個(gè)接口變量,這個(gè)變量能夠存儲(chǔ)任何實(shí)現(xiàn)該接口的對(duì)象類型。
也就是說(shuō),如果我們聲明了Men類型的接口變量m,那么這個(gè)變量就可以存儲(chǔ)Student和Employee類型的對(duì)象,還有Human類型(差點(diǎn)忘掉)。這是因?yàn)樗麄兌紝?shí)現(xiàn)了Men接口聲明的方法簽名。
如果m能夠存儲(chǔ)不同數(shù)據(jù)類型的值,我們可以輕松實(shí)現(xiàn)一個(gè)Men切片,該切片包含不同的數(shù)據(jù)類型的實(shí)例。
下面這個(gè)例子能夠幫助梳理我們上面的說(shuō)教:
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.
}
// 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
你可能已經(jīng)注意到,接口類型是一組抽象的方法集,他本身并不實(shí)現(xiàn)方法或者精確描述數(shù)據(jù)結(jié)構(gòu)和方法的實(shí)現(xiàn)方式。接口類型只是說(shuō):“兄弟,我實(shí)現(xiàn)了這些方法,我能勝任”。
值得注意的是這些數(shù)據(jù)類型沒有提及任何的關(guān)于接口的信息(我的理解是Student和Employee數(shù)據(jù)類型),方法簽名的實(shí)現(xiàn)部分也沒有包含給定的接口類型的信息。
同樣的,一個(gè)接口類型也不會(huì)去關(guān)心到底是什么數(shù)據(jù)類型實(shí)現(xiàn)了他自身,看看Men接口沒有涉及Student和Employee的信息就明白了。接口類型的本質(zhì)就是如果一個(gè)數(shù)據(jù)類型實(shí)現(xiàn)了自身的方法集,那么該接口類型變量就能夠引用該數(shù)據(jù)類型的值。
The case of the empty interface
空接口類型interface{}一個(gè)方法簽名也不包含,所以所有的數(shù)據(jù)類型都實(shí)現(xiàn)了該方法。
空接口類型在描述一個(gè)對(duì)象實(shí)例的行為上力不從心,但是當(dāng)我們需要存儲(chǔ)任意數(shù)據(jù)類型的實(shí)例的時(shí)候,空接口類型的使用使得我們得心應(yīng)手。
// a is an empty interface variable
var a interface{}
var i int = 5
s := "Hello world"
// These are legal statements
a = i
a = s
如果一個(gè)函數(shù)的參數(shù)包括空接口類型interface{},實(shí)際上函數(shù)是在說(shuō)“兄弟,我接受任何數(shù)據(jù)”。如果一個(gè)函數(shù)返回一個(gè)空接口類型,那么函數(shù)再說(shuō)“我也不確定返回什么,你只要知道我一定返回一個(gè)值就好了”。
是不是很有用處?請(qǐng)接著看。
Functions with interface parameters
以上的例子給我們展示了一個(gè)接口類型如何存儲(chǔ)滿足他的的數(shù)據(jù)類型實(shí)例,并且展示給我們?nèi)绾蝿?chuàng)建存儲(chǔ)不同數(shù)據(jù)類型實(shí)例的集合。
利用此思想,我們還可以讓函數(shù)來(lái)接受滿足特定接口類型的數(shù)據(jù)類型實(shí)例。
舉例來(lái)說(shuō),我們已經(jīng)知道fmt.Print 是一個(gè)可變參數(shù)的函數(shù),他可以接受任意數(shù)量的參數(shù)。但是你有沒有注意到,有時(shí)候我們使用的是strings、ints和floats?
事實(shí)上,如果你深入去看fmt包,你就會(huì)看到如下的接口聲明:
//The Stringer interface found in fmt package
type Stringer interface {
String() string
}
任何數(shù)據(jù)類型,只要實(shí)現(xiàn)了Stringer接口,就能夠傳遞給fmt.Print函數(shù),然后打印出該類型String()函數(shù)的返回值。
讓我們?cè)囈幌拢?/p>
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?
現(xiàn)在回頭看一下我們?cè)趺词褂胒mt.Print,我們傳遞給他參數(shù)Bob,Bob是一個(gè)Human類型的實(shí)例,然后Bob就被優(yōu)雅的打印出來(lái)。我們所做的就是是Human實(shí)現(xiàn)了String()方法。
回想一下colored boxes example的例子(這是以前章節(jié)的,但是這里我認(rèn)為不會(huì)影響大家的理解)?我們有一個(gè)Color類型,這個(gè)類型實(shí)現(xiàn)了String方法。我們重新回到那個(gè)程序,然后調(diào)用fmt.Print函數(shù)來(lái)打印結(jié)果:
//These two lines do the same thing
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
是不是很酷?
另外一個(gè)讓你喜歡上interface接口的例子就是sort包,這個(gè)包用來(lái)對(duì)int、float和string數(shù)據(jù)類型進(jìn)行排序。
我們先看一個(gè)小例子,然后給你展示一個(gè)這個(gè)包的神奇之處。
``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]
是不是很簡(jiǎn)單的工作,但是我想展示的更加吸引人。
事實(shí)上,sort包定義了一個(gè)包含三個(gè)方法簽名的接口類型:
``
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接口的文檔中我們可以看到:
type, typically a collection, that satisfies sort. Interface can be sorted by the routines in this package. The methods require that the elements of the collection be enumerated by an integer index.
所以我們?yōu)榱伺判蛞粋€(gè)給定的切片只需要實(shí)現(xiàn)這三個(gè)函數(shù)就可以了!我們來(lái)試一下吧:
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)
搞定了,如我們所預(yù)料的那樣。
我們沒有實(shí)現(xiàn)HumanGroup的排序函數(shù),所做的只是實(shí)現(xiàn)了三個(gè)函數(shù)(Len,Less和Swap),這個(gè)就是sort.Sort函數(shù)需要的全部信息。
我知道你很奇怪,你很想知道這個(gè)神奇之處是怎么實(shí)現(xiàn)的。實(shí)際上他的實(shí)現(xiàn)很簡(jiǎn)單,Sort包的排序函數(shù)接受任意類型的參數(shù),只要他實(shí)現(xiàn)了Sort接口類型。
我們嘗試了幾種不同的利用接口類型作為參數(shù)的例子,這些例子利用接口類型達(dá)到了抽象數(shù)據(jù)類型的目的。
我們接下來(lái)嘗試一下,寫一個(gè)接受特定接口類型的函數(shù)來(lái)驗(yàn)證一下我們是否理解了Interface類型。
Our own example
我們過(guò)去使用過(guò)Max(s []int) int 和 Older(s []Person) Person函數(shù)。他們都實(shí)現(xiàn)了相似的功能。實(shí)際上,實(shí)現(xiàn)一個(gè)切片的最大值就在做一件事:迭代處理和比較。
讓我們嘗試一下:
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接口包含三個(gè)方法簽名,滿足該接口的數(shù)據(jù)類型需要實(shí)現(xiàn)這三個(gè)方法。
- Len() int:必須返回集合數(shù)據(jù)結(jié)構(gòu)的長(zhǎng)度
- Get(int i) interface{}:必須返回一個(gè)在索引i的數(shù)據(jù)元素
- Bigger(i, j int) bool: 返回位于索引i和j的數(shù)值比較結(jié)果
這個(gè)排序方式的實(shí)現(xiàn)是不是很簡(jiǎn)單直接。
值得注意的是Max方法并沒有要求任何關(guān)于具體數(shù)據(jù)類型參數(shù)的信息。我們利用接口類型MaxInterface實(shí)現(xiàn)了數(shù)據(jù)抽象。
好了,就先到這里吧,再見!