当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。在使用鸭子类型编程语言中,更应该关注的是对象的行为,对能干什么?对象有什么样的能力,这和上篇文章中介绍的多态和抽象接口是类似的,如果一组对象有相似的行为功能可以抽象的划分成为一类。
在通常编程过程中我们编写一个函数,函数接受某个类型的对象,并在函数内部调用它的行为,那这个函数只能接受某个特定类型的对象。而在提倡使用鸭子类型的语言中,这个函数可能就被修改成接受某个行为集合的对象作为参数,只要某个类型对象具备这个行为功能,那么就能被这个函数准确调用和运行,反之则会出现运行时错误。
type Duck interface { Swim() // 游泳 Feathers() // 羽毛 }这里使用 Go 提供的 interface 关键字定义了一个鸭子接口类型,这个接口中提供了鸭子的两种行为:游泳和羽毛是什么样的,但是没有提供实现。
type QuackDuck interface { Quack() // 嘎嘎叫 Duck // 嵌入接口 }这样 QuackDuck 类型就拥有了之前 Duck 提供的两种抽象能力,同时还应该拥有嘎嘎叫的能力。
// 堆代码 duidaima.com // RealDuck - 真正的鸭子 type RealDuck struct { } func (RealDuck) Swim() { fmt.Println("用鸭璞向后划水") } func (RealDuck) Feathers() { fmt.Println("遇到水也不会湿的羽毛") } func (RealDuck) Quack() { fmt.Println("嘎~ 嘎~ 嘎~") } // ToyDuck - 玩具鸭 type ToyDuck struct { } func (ToyDuck) Swim() { fmt.Println("以固定的速度向前移动") } func (ToyDuck) Feathers() { fmt.Println("白色的固定的塑料羽毛") }可以看到我们定义了两种鸭子类型,一种是真正的鸭子,它还多实现了一种嘎嘎叫方法,另一个玩具鸭子只有游泳和羽毛这两种行为。这个编程方式和我们写普通的结构体方法没什么区别,只是对应的方法签名相同,其实这种方式在 Go 语言的标准库中有特别多的应用,比如:io.Reader、io.Writer 和 io.Closer。
var duck Duck duck = ToyDuck{} duck.Swim() duck.Feathers()输出
以固定的速度向前移动 白色的固定的塑料羽毛 由于玩具鸭没有嘎嘎叫的能力,所以如果你这么写编译无法通过错误实现,我们也可以用一种工厂的方式来进行调用:
func Factory(name string) Duck { switch name { case "toy": return &ToyDuck{} case "real": return &RealDuck{} default: panic("No such duck") } } // 堆代码 duidaima.com func main() { duck := Factory("toy") duck.Swim() duck.Feathers() }