1、断言
接口是编程的规范,他也可以作为函数的参数,以让函数更具备适用性。在下列示例中,有三个接口动物接口、飞翔接口、游泳接口,两个实现类鸟类与鱼类:
- 鸟类:实现了动物接口,飞翔接口
- 鱼类:实现了动物接口,游泳接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 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),对接口的类型进行判断,类型断言格式:
1 2
| t := i.(T) t, ok := i.(T)
|
- i 代表接口变量
- T 代表转换的目标类型
- t 代表转换后的变量
上述案例的 Dsiplay 就可以书写为:
1 2 3 4 5 6 7 8 9 10 11 12
| func Display(a Animal) { a.Breath() instance, ok := a.(*Bird) if ok { fmt.Println("该鸟类的名字是:", instance.Name) } else { fmt.Println("该动物不是鸟类") } }
|
2、接口类型转换
在接口定义时,其类型已经确定,因为接口的本质是方法签名的集合,如果两个接口的方法签名集合相同(顺序可以不同),则这2个接口之间不需要强制类型转换就可以相互赋值,因为 go 编译器在校验接口是否能赋值时,比较的是二者的方法集。
在上一节中,函数Display接收的是Animal接口类型,在断言后转换为了别的类型:*Bird(实现类指针类型)。
其实,断言还可以将接口转换成另外一个接口:
1 2 3 4 5 6 7 8 9
| func Display(a Animal) { instance, ok := a.(Flyer) if ok { instance.Fly() } else { fmt.Println("该动物不会飞") }
}
|
一个实现类往往实现了很多接口,为了精准类型查询,可以使用switch语句来判断对象类型:
1 2 3 4 5 6
| var v1 interfaceP{} = ... switch v := v1.(type) { case int: case string: ... }
|
3、多态
多态是面向对象的三大特性之一,即一个类型具备多种具体的表现形式。
上述示例中,鸟和鱼都实现了动物接口的 Breath 方法,即动物的 Breath 方法在鸟和鱼中具备不同的体现。我们在 new 出动物的具体对象实例时,这个对象实例也就实现了对应自己的接口方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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() }
|