12-接口类型-2-断言与多态

一 断言

接口是编程的规范,他也可以作为函数的参数,以让函数更具备适用性。在下列示例中,有三个接口动物接口、飞翔接口、游泳接口,两个实现类鸟类与鱼类:

  • 鸟类:实现了动物接口,飞翔接口
  • 鱼类:实现了动物接口,游泳接口
package main

import "fmt"

// 定义一个通用接口:动物接口
type Animal interface {
Breath() // 动物都具备 呼吸方法
}

type Flyer interface {
Fly()
}
type Swimer interface {
Swim()
}

// 定义一个鸟类:其呼吸的方式是在陆地
type Bird struct {
Name string
Food string
Kind string
}
func (b *Bird) Breath() {
fmt.Println("鸟 在 陆地 呼吸")
}
func (b *Bird) Fly() {
fmt.Printf("%s 在 飞\n", b.Name)
}

// 一定一个鱼类:其呼吸方式是在水下
type Fish struct {
Name string
Kind string
}
func (f *Fish) Breath() {
fmt.Println("鱼 在 水下 呼吸")
}
func (f *Fish) Swim() {
fmt.Printf("%s 在游泳\n", f.Name)
}

// 一个普通函数,参数是动物接口
func Display(a Animal) {
// 直接调用接口中的方法
a.Breath()
// 调用实现类的成员:此时会报错
fmt.Println(a.Name)
}

func main() {
var b = &Bird{
"斑鸠",
"蚂蚱",
"鸟类"
}
Display(b)
}

接口类型无法直接访问其具体实现类的成员,需要使用断言(type assertions),对接口的类型进行判断,类型断言格式:

t := i.(T)            //不安全写法:如果i没有完全实现T接口的方法,这个语句将会触发宕机
t, ok := i.(T) // 安全写法:如果接口未实现接口,将会把ok掷为false,t掷为T类型的0值
  • i代表接口变量
  • T代表转换的目标类型
  • t代表转换后的变量

上述案例的Dsiplay就可以书写为:

func Display(a Animal) {
// 直接调用接口中的方法
a.Breath()
// 调用实现类的成员:此时会报错
instance, ok := a.(*Bird) // 注意:这里必须是 *Bird类型,因为是*Bird实现了接口,不是Bird实现了接口
if ok {
// 得到了具体的实现类,才能访问实现类的成员
fmt.Println("该鸟类的名字是:", instance.Name)
} else {
fmt.Println("该动物不是鸟类")
}
}

二 接口类型转换

在接口定义时,其类型已经确定,因为接口的本质是方法签名的集合,如果两个接口的方法签名结合相同(顺序可以不同),则这2个接口之间不需要强制类型转换就可以相互赋值,因为go编译器在校验接口是否能赋值时,比较的是二者的方法集。

在上一节中,函数Display接收的是Animal接口类型,在断言后转换为了别的类型:*Bird(实现类指针类型):

func Display(a Animal) {
instance, ok := a.(*Bird) // 动物接口转换为了 *Bird实现类
if ok {
// 得到了具体的实现类,才能访问实现类的成员
fmt.Println("该鸟类的名字是:", instance.Name)
} else {
fmt.Println("该动物不是鸟类")
}
}

其实,断言还可以将接口转换成另外一个接口:

func Display(a Animal) {
instance, ok := a.(Flyer) // 动物接口转换为了飞翔接口
if ok {
instance.Fly()
} else {
fmt.Println("该动物不会飞")
}

}

一个实现类往往实现了很多接口,为了精准类型查询,可以使用switch语句来判断对象类型:

var v1 interfaceP{} = ...
switch v := v1.(type) {
case int:
case string:
...
}

三 多态

多态是面向对象的三大特性之一,即一个类型具备多种具体的表现形式。

上述示例中,鸟和鱼都实现了动物接口的 Breath方法,即动物的Breath方法在鸟和鱼中具备不同的体现。我们在new出动物的具体对象实例时,这个对象实例也就实现了对应自己的接口方法。

// New出Animal的函数
func NewAnimal(kind string) Animal{

switch kind {
case "鸟类":
return &Bird{}
case "鱼类":
return &Fish{}
default:
return nil
}

}

func main() {
// 获取的是动物接口类型,但是实现类是鸟类
a1 := NewAnimal("鸟类")
a1.Breath() // 鸟 在 陆地 呼吸

// 获取的是动物接口类型,但是实现类是鱼类
a2 := NewAnimal("鱼类")
a2.Breath() // 鱼 在 水下 呼吸
}