目录

一 断言

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

  • 鸟类:实现了动物接口,飞翔接口

  • 鱼类:实现了动物接口,游泳接口

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()        // 鱼 在 水下 呼吸
}