鸭子类型

title: 概念样例.
content: 考虑用于一个使用鸭子类型的语言的以下伪代码:  在样例中,每次对codice_1的调用都使用的对象(数字、列表和字符串)在继承关系中没有联系。只要对象支持“+”和“*”方法,操作就能成功。例如,翻译成Ruby或Python语言,运行结果应该是:  这样,鸭子类型在不使用继承的情况下使用了多态。唯一的要求是codice_1函数需要作为参数的对象拥有“+”和“*”方法。以下样例(Python语言)体现了鸭子测试。就codice_3函数而言,对象是一个鸭子:
title: 静态语言中的鸭子类型.
content: 一些通常的静态语言如Boo和C#第四版,有一些额外的类型注解,它们指示编译器将类的类型检查安排在运行时而不是编译时,并在编译器的输出中包含用于运行时类型检查的代码。这些附加的内容允许这些语言享受鸭子类型的大多数益处,仅有的缺点是需要在编译时识别和指定这些动态类。
title: 与其他类型系统的比较.
title: 结构类型系统.
content: 鸭子类型和结构类型相似但与之不同。结构类型由类型的结构决定类型的兼容性和等价性,而鸭子类型只由结构中在运行时所访问的部分决定类型的兼容性。Objective Caml语言使用结构类型系统。
title: 接口.
content: 接口可以提供鸭子类型的一些益处,但鸭子类型与之不同的是没有显式定义任何接口。例如,如果一个第三方Java库实现了一个使用者不允许修改的类,使用者就无法把这个类的实例用作一个自己定义的接口的实现,而鸭子类型允许这样做。
title: 模板或泛型.
content: 模板函数或方法在一个静态类型上下文中应用鸭子测试;这同时带来了静态和动态类型检查的一般优点和缺点。同时,由于在鸭子类型中,只有“在运行时被实际调用的”方法需要被实现,而模板要求实现“在编译时不能证明不可到达的”所有方法,因此鸭子类型更具有可伸缩性。  实例包括带有模板的C++语言和Java语言的泛型。
title: 批评.
content: 关于鸭子类型常常被引用的一个批评是它要求程序员在任何时候都必须很好地理解他/她正在编写的代码。在一个强静态类型的、使用了类型继承树和参数类型检查的语言中,给一个类提供未预测的对象类型更为困难。例如,在Python中,你可以创建一个称为Wine的类,并在其中需要实现press方法。然而,一个称为Trousers的类可能也实现press()方法。为了避免奇怪的、难以检测的错误,开发者在使用鸭子类型时需要意识到每一个“press”方法的可能使用,即使在语义上和他/她所正在编写工作的代码没有任何关系。  本质上,问题是:“如果它走起来像鸭子并且叫起来像鸭子”,它也可以是一只正在模仿鸭子的龙。尽管它们可以模仿鸭子,但也许你不总是想让龙进入池塘。  鸭子类型的提倡者,如吉多·范罗苏姆,认为这个问题可以通过在测试和维护代码库前拥有足够的了解来解决。  对鸭子类型的批评倾向于成为关于动态类型和静态类型的争论的更广阔的观点的特殊情形。
title: 历史.
content: Alex Martelli很早(2000年)就在发布到comp.lang.python新闻组上的一则消息中使用了这一术语。他同时对鸭子测试的错误的字面理解提出了提醒,以避免人们错误认为这个术语已经被使用。
title: 实现.
title: 在ColdFusion中.
content: web应用程序脚本语言ColdFusion允许函数参数被指定为类型为"any"。对于这种参数,任意对象都可被传入,函数调用在运行时被动态绑定。如果对象没有实现一个被调用的函数,一个可被捕获并优雅地处理的运行时异常将被抛出。在ColdFusion 8中,这也可以被一个已定义的事件onMissingMethod()而不是异常处理器处理。另一个可替代的参数类型"WEB-INF.cftags.component"限制传入参数是一个ColdFusion组件(CFC),在一个不正确的对象传入时它提供了更好的错误消息。
title: 在C#中.
content: C# 4.0实现了动态成员查询(dynamic member lookup)实现了鸭子类型化。注意下例中类方法InTheForest的参数类型被声明为dynamic。
title: 在Common Lisp中.
content: Common Lisp提供了一个面向对象的扩展(Common Lisp对象系统,简写为CLOS)。在Common Lisp中,CLOS和Lisp的动态类型使鸭子类型成为一种通用的编程风格。  使用Common Lisp,用户通常不需要查询类型,因为如果一个函数不适用,系统会抛出一个运行时错误。这个错误可以被Common Lisp的条件系统处理。在类外定义的方法也可以为指定的对象定义。  Common Lisp通常的开发风格(像SLIME一样使用Lisp REPL)也允许交互式修复:  通过这种方法,软件可以通过扩展只有部分工作的使用鸭子类型的代码来开发。
title: 在Objective-C中.
content: Objective-C,C和Smalltalk的一个交错,像Smalltalk一样,允许用户声明一个对象的类型为“id”并向它发送任何信息。发送者可以测试一个对象以了解它能不能对一个消息响应,对象可以在收到消息的时候决定响应与否,如果发送者发送了一个接收者不能响应的消息,一个异常会被抛出。因此,鸭子类型在Objective-C中被完全支持。
title: 在Python中.
content: 鸭子类型在Python中被广泛使用。Python术语表这样定义鸭子类型:  在Python中,鸭子类型的最典型例子就是类似file的类。这些类可以实现codice_4的一些或全部方法,并可以用于codice_4通常使用的地方。例如,codice_6实现了一个用于访问gzip压缩的数据的类似file的对象。codice_7允许把一个Python字符串视作一个文件。套接字(socket)也和文件共同拥有许多相同的方法。然而套接字缺少codice_8方法,不能用于codice_6可以使用的所有地方。这体现了鸭子类型的可伸缩性:一个类似file的对象可以实现它有能力实现的方法,且只能被用于它有意义的情形下。  EAFP原则描述了异常处理的使用。例如相对于检查一个自称为类似Duck的对象是否拥有一个"quack()"方法(使用codice_10),人们通常更倾向于用异常处理把对"quack"的调用尝试包裹起来:  这个写法的优势在于它鼓励结构化处理其他来自类的错误(这样的话,例如,一个不能完成quack的Duck子类可以抛出一个“QuackException”,这个异常可以简单地添加到包裹它的代码,并不需要影响更多的代码的逻辑。同时,对于其他不同类的对象存在不兼容的成员而造成的命名冲突,它也能够处理(例如,假设有一个医学专家Mallard有一个布尔属性将他分类为“quack=True”,试图执行Mallard.quack()将抛出一个TypeError)。  在更实际的实现类似file的行为的例子中,人们更倾向于使用Python的异常处理机制来处理各种各样的可能因为各种程序员无法控制的环境和operating system问题而发生的I/O错误。在这里,“鸭子类型”产生的异常可以在它们自己的子句中捕获,与操作系统、I/O和其他可能的错误分别处理,从而避开复杂的检测和错误检查逻辑。

Summary: 鸭子类型()在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试(见下面的“历史”章节),“鸭子测试”可以这样表述:

Duck-Typing: Duck-Typing ist ein Konzept der objektorientierten Programmierung, bei dem der Typ eines Objektes nicht durch seine Klasse beschrieben wird, sondern durch das Vorhandensein bestimmter Methoden oder Attribute.