十六、接口
1 定义接口
在面向对象中,接口规范了一个对象的行为,接口指定对象必须实现的方法,而实现的细节由对象自己去完成。go语言中同样提供接口,它把所有的具有共性的方法定义在一起,只要任何其他类型只要实现了这些方法就是实现了这个接口。
接口的定义语法如下:
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
接口是方法的集合,要实现这个接口就必须实现这些方法。示例:
// 定义接口
type Animal interface {
sleep()
eat()
}
// 定义结构体
type Dog struct {
Name string
Age int
}
// 为Dog绑定方法实现接口
func (dog Dog) sleep() {
fmt.Println(dog.Name + "is sleeping!")
}
//为Dog绑定方法实现接口
func (dog Dog) eat() {
fmt.Println(dog.Name + "is eating!")
}
func main() {
d := Dog{
Name: "小狗",
Age: 5,
}
d.eat()
d.sleep()
}
上面定义了一个Animal
接口,要求实现两个方法。然后定义一个Dog
结构体,为它绑定这两个方法,go与java不同,不需要显式声明实现的接口,只要这个类型实现了接口的所有方法,就默认实现了这个接口。但是,这样定义与之前为结构体绑定方法没有区别,把接口去掉程序仍然可以正常工作。
2 接口的作用
接口的实际应用是实现多态。接口是一种数据类型,所以我们可以定义接口类型的变量,然后将其它实现接口的结构赋值给它,实现多态。
示例:
// 定义接口
type Animal interface {
eat()
}
// 定义结构体
type Dog struct {
Name string
Age int
}
type Bird struct {
Name string
}
//为Dog绑定方法实现接口
func (dog Dog) eat() {
fmt.Println(dog.Name + "只吃肉")
}
//为Bird绑定方法实现接口
func (bird Bird) eat() {
fmt.Println(bird.Name + "吃虫子")
}
func main() {
// 定义nil接口
var animal Animal
animal = Bird{Name: "麻雀"}
animal.eat() //麻雀吃虫子
animal = Dog{Name: "小狗"}
animal.eat() //小狗只吃肉
}
接口是动态的,就像切片是对数组的动态引用一样,接口也是类似的工作方式。
上面的程序中,我们定义了Animal
接口,且Bird
和Dog
都实现了这个接口,然后我们在main函数中,先定义一个未赋值的接口。首先将Bird
指定给它,此时animal的动态类型就是Bird
,此时访问animal
接口时,只会返回动态值的类型(eat方法),它的静态类型(Name
字段)保持隐藏。然后,我们将Dog
指定给它,此时的动态类型就是Dog
,我们访问的eat
方法就是绑定给Dog
的方法,由此实现了多态。
3 类型断言
先介绍类型断言的语法:
value, ok := i.(Type)
上节提到接口不能访问低层的静态字段,我们可以使用类型断言来获取这些基础值。
// 定义接口
type Animal interface {
eat()
}
// 定义结构体
type Dog struct {
Name string
Age int
}
type Bird struct {
Name string
}
//为Dog绑定方法实现接口
func (dog Dog) eat() {
fmt.Println(dog.Name + "只吃肉")
}
//为Bird绑定方法实现接口
func (bird Bird) eat() {
fmt.Println(bird.Name + "吃虫子")
}
func main() {
// 定义接口
var animal Animal
animal = Dog{Name: "小狗", Age: 5}
// 类型断言,判断animal的具体类型是否为Dog
value, ok := animal.(Dog)
fmt.Println(ok)
// 断言成功,获取底层字段内容
fmt.Println(value.Age)
fmt.Println(value.Name)
}
其中,类型断言如果正确,就将此时的底层值赋值给animal
,将true
赋值给ok
;反之,animal
为该类型的零值,ok=false
。
4 多接口
一个类型可以实现多个接口。例:
// 定义接口
type Animal interface {
eat()
}
type Habit interface {
football()
}
// 定义结构体
type Dog struct {
Name string
}
// 实现Animal接口
func (dog Dog) eat() {
fmt.Println(dog.Name + "只吃肉")
}
// 实现Habit接口
func (dog Dog) football() {
fmt.Println(dog.Name + "爱玩足球")
}
上面例子中,Dog实现了两个接口,也就意味着可以将Dog
类型的值赋给Animal
或Habit
接口类型的变量。
5 空接口
当接口没有方法时,它被称为空接口。 除了有名的空接口,还可以定义匿名的。
匿名空接口的定义为 interface{}
,一般用于函数的形式参数。空接口不规范任何方法,也就意味着所有类型都实现了空接口。举例说明:
type str string
// 函数接收一个空接口
func foo(in interface{}) {
fmt.Println(in)
}
func main() {
s := str("hello world")
i := 5
foo(s)
foo(i)
}
我们自定义一个str
类型,还有一个int
类型的数据,将它传递给foo
,由于任何类型都实现空接口,空接口作为函数的参数可以保存任何类型的值,所以这个函数可以接收所有类型的数据。
6 接口嵌套
顾名思义,举例
// 定义接口
type Animal interface {
eat()
}
type Habit interface {
football()
}
type Dog interface {
sleep()
Animal
Habit
}
定义的Dog
接口里,包含Animal
和Habit
,所以要实现Dog
必须实现这些接口(类似于面向对象的继承)