断言
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("该动物不会飞")
}
}
多态
多态是面向对象的三大特性之一,即一个类型具备多种具体的表现形式,在go中多态利用接口来实现,而不像Java依据继承和向上转型来实现。
上述示例中,鸟和鱼都实现了动物接口的 Breath方法,即动物的Breath方法在鸟和鱼中具备不同的体现。我们在new出动物的具体对象实例时,这个对象实例也就实现了对应自己的接口方法。
// New出Animal的函数
func CreatAnimal(kind string) Animal{
switch kind {
case "鸟类":
return &Bird{}
case "鱼类":
return &Fish{}
default:
return nil
}
}
func main() {
// 获取的是动物接口类型,但是实现类是鸟类
a1 := CreatAnimal("鸟类")
a1.Breath() // 鸟 在 陆地 呼吸
// 获取的是动物接口类型,但是实现类是鱼类
a2 := CreatAnimal("鱼类")
a2.Breath() // 鱼 在 水下 呼吸
}