Go语言接口实现多态(鸭子类型)
很多编程语言都支持鸭子类型(Duck Typing),通常鸭子类型是动态编程语言用来实现多态的一种方式。
图 1 大黄鸭
理解鸭子类型之前,我们先看上图,它是曾经很受欢迎的大黄鸭,从人们认知的角度分析,它不是一只鸭子,因为它没有生命,更不要说它具备鸭子的本能,但是从鸭子类型角度来看,它就是一只鸭子。鸭子类型的原意是:只要走起来像鸭子,或者游泳姿势像鸭子,或者叫声像鸭子,那么它就是一只鸭子。用官方术语解释:鸭子类型只关心事物的外部行为而非内部结构。
我们知道接口方法必须与结构体进行绑定,每次使用的时候都要创建接口变量、创建结构体实例化变量、结构体与接口绑定等,在使用上造成诸多不便。如果将接口与结构体的绑定过程以函数实现,只要传入结构体实例化变量就能自动执行接口方法,示例如下:
运行上述代码,运行结果为:
我们再通俗一点理解,函数 speaking() 只要传入结构体实例化变量的内存地址就能将结构体视为“鸭子”,无论结构体代表什么事物,它就是一只鸭子,这与指鹿为马颇有几分相似。
总的来说,Duck Typing 是在接口、结构体和结构体方法已定义的前提下,以函数方式封装结构体和接口的绑定操作,外部使用只需传入结构体实例化变量或指针变量就能调用接口中的方法。
图 1 大黄鸭
理解鸭子类型之前,我们先看上图,它是曾经很受欢迎的大黄鸭,从人们认知的角度分析,它不是一只鸭子,因为它没有生命,更不要说它具备鸭子的本能,但是从鸭子类型角度来看,它就是一只鸭子。鸭子类型的原意是:只要走起来像鸭子,或者游泳姿势像鸭子,或者叫声像鸭子,那么它就是一只鸭子。用官方术语解释:鸭子类型只关心事物的外部行为而非内部结构。
我们知道接口方法必须与结构体进行绑定,每次使用的时候都要创建接口变量、创建结构体实例化变量、结构体与接口绑定等,在使用上造成诸多不便。如果将接口与结构体的绑定过程以函数实现,只要传入结构体实例化变量就能自动执行接口方法,示例如下:
package main import "fmt" // 定义接口 type actions interface { speak(content string) } // 定义结构体 type duck struct { name string } type cat struct { name string } // 定义结构体方法 func (d *duck) speak(content string) { fmt.Printf("%v在说话:%v\n", d.name, content) } func (c *cat) speak(content string) { fmt.Printf("%v在说话:%v\n", c.name, content) } // 定义函数 func speaking(a actions, content string) { a.speak(content) } func main() { // 实例化结构体 d := duck{name: "唐老鸭"} c := cat{name: "凯蒂猫"} // 调用函数 speaking(&d, "嘎嘎") speaking(&c, "喵喵") }上述代码的实现过程如下:
- 定义接口 actions 和接口方法 speak(),接口方法的参数 content 为字符串类型;分别定义结构体 duck 和 cat,它们只有一个结构体成员 name。
- 分别为结构体 duck 和 cat 定义结构体方法 speak(),并使用指针接收者,对应接口方法 speak()。
- 定义函数 speaking(),函数参数 a 代表接口 actions,参数 content 是字符串类型,函数中使用参数 a 调用接口方法 speak(),并将参数 content 作为接口方法 speak() 的参数。
- 主函数 main() 对结构体 duck 和 cat 执行实例化,对应实例化变量 d 和 c;然后调用函数 speaking(),分别将变量 d、c 的内存地址和相应字符串变量作为 speaking() 的函数参数。
运行上述代码,运行结果为:
唐老鸭在说话:嘎嘎
凯蒂猫在说话:喵喵
我们再通俗一点理解,函数 speaking() 只要传入结构体实例化变量的内存地址就能将结构体视为“鸭子”,无论结构体代表什么事物,它就是一只鸭子,这与指鹿为马颇有几分相似。
总的来说,Duck Typing 是在接口、结构体和结构体方法已定义的前提下,以函数方式封装结构体和接口的绑定操作,外部使用只需传入结构体实例化变量或指针变量就能调用接口中的方法。