接口

十六、接口

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接口,且BirdDog都实现了这个接口,然后我们在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类型的值赋给AnimalHabit接口类型的变量。

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接口里,包含AnimalHabit,所以要实现Dog必须实现这些接口(类似于面向对象的继承)

上一篇:Java向上转型与向下转型知识点整理


下一篇:reloaddata,btn,info,play,timer,