原文:http://floss.zoomquiet.io/data/20120904000006/index.html
追加:
http://blog.zhaojie.me/2013/04/why-i-dont-like-go-style-interface-or-structural-typing.html
从老赵的博文里学到更精确的说法“Structural Typing”,属于吐槽文,go粉慎入
什么是duck typing?
在面向对象的编程语言中,当某个地方(比如某个函数的参数)需要符合某个条件的变量(比如要求这个变量实现了某种方法)时,什么是判断这个变量是否“符合条件”的标准?
如果某种语言在这种情况下的标准是: 这个变量的类型是否实现了这个要求的方法(并不要求显式地声明),那么这种语言的类型系统就可以称为 duck typing
Duck Typing
听起来有点不好理解,举例更为直观。看下面一段简单的 Python 代码:
1 def greeting(a): 2 return a.sayHello() 3
4 class Duck(object): 5 def sayHello(self): 6 print('ga ga ga!') 7
8 class Person(object): 9 def sayHello(self): 10 print('Hello!') 11
12 class Unknown(object): 13 pass
14
15 duck = Duck() 16 person = Person() 17 u = Unknown() 18 u.sayHello = duck.sayHello 19
20 greeting(duck) 21 greeting(person) 22 greeting(u) # 最后的输出为 'ga ga ga! Hello! ga ga ga!'
从哪里可以看出 Python 是 duck typing 呢?
上面这段 Python 代码中, greeting 函数对参数 a 只有一个要求: a 必须实现 sayHello 这个方法。因为 Duck 类和 Person 类都实现了 sayHello,那么这两个类型的实例,duck 和 person,都可以用作 greeting 的参数。甚至一个空白的类 Unknown 的对象 u,只要我们给它加上一个 sayHello 的属性(上面代码中第18 行),它也能作为 greeting 的参数。
与其它类型系统的区别
以 Java为例, 一个类必须显式地声明:“我实现了这个接口。是这样实现的。” 然后才能用在任何要求这个接口的地方。
如果你有一个第三方的 Java 库,这个库中的某个类没有声明它实现了某个你自定义的接口,那么即使这个类中真的有那些相应的方法,你也不能把这个类的对象用在那些要求你自定义的那个接口的地方。但如果在某种 duck typing的语言中, 你就可以这样做,因为它不要求一个类显式地声明它实现了某个接口。
Duck typing 的准则是 “If you can do it,you can be used here”。Wikipeida 上的一个非常形象的解释是:
When I see a bird that walks like a duck and swims like a duck and quacks like a duck,I call that bird a duck.
Golang 的类型系统
一般来讲,使用 duck typing 的编程语言往往被归类到“动态类型语言”或者“解释型语言”里,比如 Python,Javascript,Ruby 等等;而其它的类型系统往往被归到“静态类型语言“中,比如 C/C++/Java。
动态类型的好处很多,使用过 Python 的人都知道写代码写起来很快。但是缺陷也是显而易见的:错误往往要在运行时才能被发现。比如上面的 greeting 函数,你可以传递任何一个变量作为参数,但是要是这个变量没有 sayHello 这个方法或者属性,那么程序运行时就会出错。相反,静态类型语言往往在编译时就是发现这类错误:如果某个变量的类型没有显式声明实现了某个方法/接口,那么,这个变量就不能用在要求一个实现了这个接口的地方。
Go 的类型系统采取了折中的办法:
- 静态类型系统
- 一个类型不需要显式地声明它实现了某个接口
- 但仅当某个变量的类型实现了某个接口的方法,这个变量才能用在要求这个接口的地方。
听起来很绕,看代码: