golang 接口
在本章中,将要学习一个新的领域。我们将学习使用面向对象编程的灵魂去构建程序,让我们一起做这件事吧。 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 %sn",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 %sn",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 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 fmt.Printf("Hi,h.phone) } //A human can sing a song fmt.Println("La la la la...",lyrics) } //Employee's method overrides Human's one fmt.Printf("Hi,e.phone) //Yes you can split into 2 lines here. } // Interface Men is implemented by Human,Student and Employee 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","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: 值得注意的是这些数据类型没有提及任何的关于接口的信息(我的理解是Student和Employee数据类型),方法签名的实现部分也没有包含给定的接口类型的信息。 同样的,一个接口类型也不会去关心到底是什么数据类型实现了他自身,看看Men接口没有涉及Student和Employee的信息就明白了。接口类型的本质就是如果一个数据类型实现了自身的方法集,那么该接口类型变量就能够引用该数据类型的值。 The case of the empty interface 空接口类型interface{}一个方法签名也不包含,所以所有的数据类型都实现了该方法。 空接口类型在描述一个对象实例的行为上力不从心,但是当我们需要存储任意数据类型的实例的时候,空接口类型的使用使得我们得心应手。 // a is an empty interface variable
// These are legal statements
如果一个函数的参数包括空接口类型interface{},实际上函数是在说“兄弟,我接受任何数据”。如果一个函数返回一个空接口类型,那么函数再说“我也不确定返回什么,你只要知道我一定返回一个值就好了”。 是不是很有用处?请接着看。 Functions with interface parameters 以上的例子给我们展示了一个接口类型如何存储满足他的的数据类型实例,并且展示给我们如何创建存储不同数据类型实例的集合。 利用此思想,我们还可以让函数来接受满足特定接口类型的数据类型实例。 举例来说,我们已经知道fmt.Print 是一个可变参数的函数,他可以接受任意数量的参数。但是你有没有注意到,有时候我们使用的是strings、ints和floats? 事实上,如果你深入去看fmt包,你就会看到如下的接口声明: //The Stringer interface found in fmt package String() string } 让我们试一下: package main "fmt" "strconv" //for conversions to and from string ) type Human struct { name string age int phone string } //Returns a nice string representing a Human //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? 回想一下colored boxes example的例子(这是以前章节的,但是这里我认为不会影响大家的理解)?我们有一个Color类型,这个类型实现了String方法。我们重新回到那个程序,然后调用fmt.Print函数来打印结果: //These two lines do the same thing 另外一个让你喜欢上interface接口的例子就是sort包,这个包用来对int、float和string数据类型进行排序。 我们先看一个小例子,然后给你展示一个这个包的神奇之处。 package main func main() { // let's use Ints function that comes in sort The list is: [1 23 65 11 0 3 233 88 99] 事实上,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) } 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 "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: The sorted group is: 我们没有实现HumanGroup的排序函数,所做的只是实现了三个函数(Len,Less和Swap),这个就是sort.Sort函数需要的全部信息。 我知道你很奇怪,你很想知道这个神奇之处是怎么实现的。实际上他的实现很简单,Sort包的排序函数接受任意类型的参数,只要他实现了Sort接口类型。 我们尝试了几种不同的利用接口类型作为参数的例子,这些例子利用接口类型达到了抽象数据类型的目的。 我们接下来尝试一下,写一个接受特定接口类型的函数来验证一下我们是否理解了Interface类型。 Our own example 我们过去使用过Max(s []int) int 和 Older(s []Person) Person函数。他们都实现了相似的功能。实际上,实现一个切片的最大值就在做一件事:迭代处理和比较。 让我们尝试一下: package main "fmt" "strconv" ) //A basic Person struct name string age int } //Some slices of ints,floats and Persons 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 //Get implementation for our three types //Bigger implementation for our three types 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 return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } /*
*/ 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,222} fslice := Float32Slice{1.99,3.14,24.8} group := PersonSlice{ Person{name:"Bart",Person{name:"Bob",Person{name:"Gertrude",Person{name:"Paul",Person{name:"Sam",Person{name:"Jack",Person{name:"Martha",Person{name:"Leo",} //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 = Max(group) fmt.Println("The oldest person in the group is:",m) } The biggest integer in islice is : 222 (1) Len() int:必须返回集合数据结构的长度 这个排序方式的实现是不是很简单直接。 值得注意的是Max方法并没有要求任何关于具体数据类型参数的信息。我们利用接口类型MaxInterface实现了数据抽象。 好了,就先到这里吧,再见! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |