golang不像其他面向对象语言,语法上不支持类和继承等概念。而golang就是一个纯粹的面向接口编程的语言。今天就来走一遍golang的接口学习。
接口怎么定义呢?使用关键字interface
type 接口名 interface{}
就这么简单,一个接口就定义好了,但是这个接口,熟悉其他语言的人都应该知道,接口顾名思义就是拿来给别人用的东西,用什么呢?当然是用方法,所以里面还得有方法,这样才叫一个真正的接口。
type MyInterface interface {
MyMethod() string
}
就这样,这就是一个有方法的接口了。由此,又可以给出接口的一个特性:接口是一组没有具体实现的方法集合。
那都说了,它里面的方法是没有实现的,也就是说,接口定义好了,现在知道这接口里面能干啥,但是还没人干,那谁干呢?(这里就又一个衍生知识点 duck typing(鸭子类型),有兴趣的朋友,可以自行去深入了解一下)golang就比较民主,谁用谁去干(即谁用谁实现),这就跟java、c++等语言不一样了,接口定义好了,必须又一个实现类,那这个接口才算真正的接口。golang不是的,它比较自由。想用,就自己去实现。
package main
import "fmt"
// People接口
type People interface {
ReturnName() string
}
// student"类"
type Student struct{
Name string
}
// student实现了People的接口
func (s Student) ReturnName() string {
return s.Name
}
// 使用接口
func UseInterfaceMethod(p People){
result := p.ReturnName()
fmt.Println(result)
}
func main() {
var p People
student := Student{Name:"海马"}
p = student
UseInterfaceMethod(p)
// 结果为:海马
}
这里可以看到,Student实现了People中的方法(当然它里面也就只有一个方法,可以有多个,当全部被Student实现),我们就可以说这个Student实现了People这个接口,那么Student就可以理解为一个实现类,然后用这个接口的引用指向这个实现类的实例,那个就可以使用这个接口的方法了,但是结果肯定就只能是Student这个实现类的方法实现逻辑所产生的。
package main
import "fmt"
// People接口
type People interface {
ReturnName() string
}
// student"类"
type Student struct{
Name string
}
// human"类"
type Human struct{
}
// student实现了People的接口
func (s Student) ReturnName() string {
return s.Name
}
// human实现了People的接口
func (h Human) ReturnName() string {
return "Human result"
}
func UseInterfaceMethod(p People){
name := p.ReturnName()
fmt.Println(name)
}
func main() {
var p People
student := Student{Name:"海马"}
p = student
UseInterfaceMethod(p)
// 结果为:海马
human := Human{}
p = human
UseInterfaceMethod(p)
// 结果为:Human result
}
由此可以看出,这个接口,我换用Human“类”实现了,方法逻辑是返回Human result。现在接口的引用重新指向了Human的实例,给到别人使用的时候,最后就变成了Human对方法实现的新逻辑产生的结果。所以说,golang就很灵活的实现了多态,接口引用还是那个引用,但是指向的不同实例(必须是实现了它的方法的实例),调用同一个方法,最终结果也不一样。
从上面的几个例子中可以看到,一个接口可以被任意对象实现,同样,一个接口也可以实现多个接口。
任意类型都实现了空interface(也就是包含0个method的interface),所以有时候可以把interface当作泛型来使用,一个方法的参数列表可以是interface,但是在方法内部使用的时候必须要做类型断言(即判断传进来的参数的类型是不是想要的),因为在golang中没有泛型的概念,并不会编译时绑定类型。而类型断言的方式有两种:Comma-ok断言和switch测试
package main
import (
"fmt"
"strconv"
)
// Human对象
type Human struct{
name string
age int
phone string
}
// 通过定义interface参数,让函数接受各种类型的参数
// 通过这个Method(方法),Human对象实现了fmt.Stringer接口
// Stringer接口是fmt.Println()的参数,最终使得Human对象可以作为fmt.Println的参数被调用
func (h Human) String() string {
return "<" + h.name + " - " + strconv.Itoa(h.age) + " years - phone " + h.phone + ">"
}
// 空接口
type Element interface{}
type List []Element
// 定义Person对象
type Person struct{
name string
age int
}
func (p Person) String() string {
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
}
func main() {
Lucy := Human{"Lucy", 29, "1866666666"}
fmt.Println("This is a Human" , Lucy)
list := make(List, 3)
list[0] = 100
list[1] = "This is a String"
list[2] = Person{"HaiMa", 25}
// Comma-ok断言 (实际开发中用得最多)
for index, element := range list {
// 判断变量的类型 格式:value, ok = element(T)
// value是interface变量的值,ok是bool类型,element是interface的变量,T是断言的interface变量的类型
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and it's value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is an int and it's value is %s\n", index, value)
}else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is an int and it's value is %s\n", index, value.name)
}else{
fmt.Printf("list[%d] is a diffent type\n", index)
}
}
// switch
for index, element := range list {
// 注意:element.(type)语法不能在switch外的任何逻辑中使用
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and it's value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is an int and it's value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is an int and it's value is %s\n", index, value.name)
default:
fmt.Printf("list[%d] is a diffent type\n", index)
}
}
}