本文章翻譯自《Let's learn Go》的“Interfaces: the awesomesauce of Go”一節,原文地址:http://go-book.appspot.com/interfaces.html。
讓我們重新梳理一下以前的內容。我們了解了基本的數據類型,我們學習了如何利用已有的數據類型構造出一個新的數據類型。我們學習了基本的控制流語句,我們將要結合這些知識構建一些簡單的應用程序。
接下來我們會發現函數實際上也是一種數據,他們具有自己的值和類型。我們將要學習關于方法的基本知識。我們使用方法來構建作用于數據上的函數,從而使某個數據類型完成特定的功能。
在本章中,將要學習一個新的領域。我們將學習使用面向對象編程的靈魂去構建程序,讓我們一起做這件事吧。
What is an interface?
簡單的說,接口就是一組方法簽名的集合。我們使用一個接口來識別一個對象的能夠進行的操作。
舉例來說,在以前的章節中,我們看到Student和Emlpoyee都可以執行SayHi函數,但是他們的運行結構是不同的,但是這個是無關緊要的,本質是他們都可以說“Hi”(這部分下邊的內容會有體現)。
我們假設Student和Employee實現了另外一個共同的函數Sing。Employ實現了SpendSalary函數,與此相對應Student實現了BorrowMoney函數。
所以Student實現了SayHi、Sing和BorrowMoney函數,Employee實現了SayHi、Sing和SpendSalary函數。
這些方法的集合就是Student和Employee滿足的接口類型。舉例來說,Student和Employee都滿足包含SayHi和Sing方法簽名的接口。但是Employee不能滿足包含SayHi、Sing和BorrowMoney的接口類型,因為Employee沒有實現方法BorrowMoney。
The interface type
接口類型實際上是一組方法簽名的清單,我們將遵循下面的接口約束:
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)
}
正如你所看到的那樣,一個接口可以被任意數量的類型滿足,在這里Student和Employee都實現了Men接口。
并且,一個類型可以實現任意數量的接口,在這里,Student實現了Men和YoungChap接口,Employee實現了Men和ElderlyGent接口。
最后需要說明的是,每個類型都實現了一個空接口interface{},大概你要說,這意味著該類型沒有方法,我們重新聲明一下interface{}的意義。
你可能自以為發現接口類型的意義:
非常酷,接口類型的意義就是描述數據類型的行為,以及數據類型的共性特征
然而事實上,接口類型的意義遠遠不止于此。
順便說一下,我說過空接口意味著不包含方法簽名嗎?
Interface values
因為接口也是一種類型,你會困惑于一個接口類型的值到底是什么。
有一個好消息就是:如果你聲明了一個接口變量,這個變量能夠存儲任何實現該接口的對象類型。
也就是說,如果我們聲明了Men類型的接口變量m,那么這個變量就可以存儲Student和Employee類型的對象,還有Human類型(差點忘掉)。這是因為他們都實現了Men接口聲明的方法簽名。
如果m能夠存儲不同數據類型的值,我們可以輕松實現一個Men切片,該切片包含不同的數據類型的實例。
下面這個例子能夠幫助梳理我們上面的說教:
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
你可能已經注意到,接口類型是一組抽象的方法集,他本身并不實現方法或者精確描述數據結構和方法的實現方式。接口類型只是說:“兄弟,我實現了這些方法,我能勝任”。
值得注意的是這些數據類型沒有提及任何的關于接口的信息(我的理解是Student和Employee數據類型),方法簽名的實現部分也沒有包含給定的接口類型的信息。
同樣的,一個接口類型也不會去關心到底是什么數據類型實現了他自身,看看Men接口沒有涉及Student和Employee的信息就明白了。接口類型的本質就是如果一個數據類型實現了自身的方法集,那么該接口類型變量就能夠引用該數據類型的值。
The case of the empty interface
空接口類型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{},實際上函數是在說“兄弟,我接受任何數據”。如果一個函數返回一個空接口類型,那么函數再說“我也不確定返回什么,你只要知道我一定返回一個值就好了”。
是不是很有用處?請接著看。
Functions with interface parameters
以上的例子給我們展示了一個接口類型如何存儲滿足他的的數據類型實例,并且展示給我們如何創建存儲不同數據類型實例的集合。
利用此思想,我們還可以讓函數來接受滿足特定接口類型的數據類型實例。
舉例來說,我們已經知道fmt.Print 是一個可變參數的函數,他可以接受任意數量的參數。但是你有沒有注意到,有時候我們使用的是strings、ints和floats?
事實上,如果你深入去看fmt包,你就會看到如下的接口聲明:
//The Stringer interface found in fmt package
type Stringer interface {
String() string
}
任何數據類型,只要實現了Stringer接口,就能夠傳遞給fmt.Print函數,然后打印出該類型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,我們傳遞給他參數Bob,Bob是一個Human類型的實例,然后Bob就被優雅的打印出來。我們所做的就是是Human實現了String()方法。
回想一下colored boxes example的例子(這是以前章節的,但是這里我認為不會影響大家的理解)?我們有一個Color類型,這個類型實現了String方法。我們重新回到那個程序,然后調用fmt.Print函數來打印結果:
//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包,這個包用來對int、float和string數據類型進行排序。
我們先看一個小例子,然后給你展示一個這個包的神奇之處。
``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包定義了一個包含三個方法簽名的接口類型:
``
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.
所以我們為了排序一個給定的切片只需要實現這三個函數就可以了!我們來試一下吧:
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)
搞定了,如我們所預料的那樣。
我們沒有實現HumanGroup的排序函數,所做的只是實現了三個函數(Len,Less和Swap),這個就是sort.Sort函數需要的全部信息。
我知道你很奇怪,你很想知道這個神奇之處是怎么實現的。實際上他的實現很簡單,Sort包的排序函數接受任意類型的參數,只要他實現了Sort接口類型。
我們嘗試了幾種不同的利用接口類型作為參數的例子,這些例子利用接口類型達到了抽象數據類型的目的。
我們接下來嘗試一下,寫一個接受特定接口類型的函數來驗證一下我們是否理解了Interface類型。
Our own example
我們過去使用過Max(s []int) int 和 Older(s []Person) Person函數。他們都實現了相似的功能。實際上,實現一個切片的最大值就在做一件事:迭代處理和比較。
讓我們嘗試一下:
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接口包含三個方法簽名,滿足該接口的數據類型需要實現這三個方法。
- Len() int:必須返回集合數據結構的長度
- Get(int i) interface{}:必須返回一個在索引i的數據元素
- Bigger(i, j int) bool: 返回位于索引i和j的數值比較結果
這個排序方式的實現是不是很簡單直接。
值得注意的是Max方法并沒有要求任何關于具體數據類型參數的信息。我們利用接口類型MaxInterface實現了數據抽象。
好了,就先到這里吧,再見!