InfoQ

InfoQ

新闻

我的书签

登录注册 以永久保存书签。

该内容已经被标记书签!

标记书签错误,请重试!

Duck Typing与协议 vs. 继承

作者 Werner Schuster 译者 郑柯 发布于 2007年12月5日

领域
架构 & 设计,
语言 & 开发
主题
设计 ,
面向对象设计 ,
语言 ,
编程 ,
Ruby ,
架构
标签
Erlang ,
SmallTalk ,
语言

在ruby-core邮件列表中,一个关于Ruby中动态类型的问题引发了一系列讨论

根据irb中的运行结果:

>> StringIO.is_a?(IO)
>> =>false

对我来说,这看起来不合逻辑。是有意而为之吗?如果是的话,为什么?

有一种答案是这样的:

因为StringIO没有使用IO作为它的基类实现,所以它不是IO。

不过这又有什么关系呢?如果你使用标准的Duck Typing,这根本就无所谓。使用Duck Typing时,类的层次关系无关紧要。类只不过是Duck Typing的一种实现细节。你也不用关心#is_a?、#respond_to?等方法。只要假定对象提供你要调用的方法,然后调用它们就可以了。

原来的问题(以及邮件列表辩论中出现的一些帖子)应该是来自对OOP中继承所担当的角色的混淆。StringIO和IO不需要同样的超类,因为它们之间没有任何共同之处,除一点之外:它们支持的一系列方法。

看来这就是Duck Typing的基本想法:让对象可以响应一系列方法就够了,没必要要求它属于某个类型。

此处没有什么新概念,也不是Ruby特有的东西。将“方法”这个词换成OOP的术语“消息”,上面的句子就变为:“【……】让对象可以响应一系列消息”。听起来真的很像是某个(网络)协议(Protocol)的概念。毕竟,如果一个系统可以理解并响应诸如“PUT”、“GET”、“POST”等以特定的方式发送给它的消息,就可以说它理解HTTP协议。

让我们进一步看看这个想法:某个客户端,比如说Web浏览器,只关心它通信的服务器是否理解HTTP,它才不管服务器是什么类型呢。总之:如果Mosaic浏览器被硬编码为需要另一端的服务器是NCSA或Netscape的HTTP服务器,互联网恐怕就不会像现在这样蓬勃发展了。通过忽略通信另一端的类型,并且只要求以特定的方式回应“GET”消息,Web开发的两端(服务器端与客户端)都可以彼此独立地进行演化。

这种方式与协议的类似之处,OOP编程语言的使用者们在很久以前就已经很明白了。SmalltalkObjectiveC,作为动态OOP语言,早就使用“协议”(Protocol)这个词来指代此概念了。

协议的概念当然很有用,正好用来给特定的信息定一个名称。这也有助于澄清上述邮件列表中争论的问题。StringIO与IO共享的不是共同的祖先,而是共同的协议。

此概念并不仅限于动态语言。在一些静态语言中,比如Java就通过引入接口,而向前跨进了一大步。接口被用来:

  • 命名一系列信息
  • 静态检查一个类是否实现了这些信息

虽然在一些语言中,比如C++,通过抽象基类和多继承也能做得到,但抽象基类混淆了继承和协议的概念。

当然,静态检查的保障是有限的 :接口只能检查一些有特定签名的方法——它不能保证方法的行为,甚或是方法的返回值,甚至也不能保证方法是可以被调用的——比如说在使用Java的Collection API的时候,要小心应对Java的java.lang.UnsupportedOperationException。

毫无疑问,接口也有它们自己的问题,比如演化性。改变一个接口,会破坏所有实现了该接口的类。像Eclipse这样的大型项目,会提供API演化的指导建议,其中规定了一个接口在发布之后就不可以再发生变化。这样的状况下,有时会建议大家多使用抽象类而不是接口,因为抽象类可以添加新的函数,但令其实现为空,这样子类就可以,但是不必实现这些新函数。当然,这个问题可以通过定义粒度合适的接口来解决,每个接口对应一个方法,并且通过对已有接口的扩展来搭建完整的接口集合,每个接口对应一定单独的API调用。

要解决保证一个对象支持某个协议这个问题,在Ruby中,可以通过将接口检查委托给一个谓语函数(predicate function)来完成。如下示例:


def supports_api?(obj)
 obj.respond_to?(:length) && obj.respond_to?(:at)
end 

此做法可行,但是同时会带来一个小问题:respond_to?只对定义在一个类中的方法起作用。如果一个对象用method_missing来实现它的部分接口(例如一个代理),虽然对这些方法的调用没有问题,但针对它们调用respond_to?会返回false。这就是说respond_to?检查可以工作,但是它不适合用来替换静态检查(Rick de Natale将这种编码风格称为“Chicken Typing”,这是防御性编程的一种)。

另一种静态定义细粒度接口的方式是Scala语言的结构化类型

def setElementText(element : {def setText(text : String)}, text : String) = 
{
element.setText(text.trim()
.replaceAll("\n","")
.replaceAll("\t",""))
}

此函数需要element参数有一个setText(text:String)方法,但是它不关心对象的特定类型。

将精力集中于某个对象的特定类型或类层次结构同样会限制灵活性。需要一个int类型参数的接口只能通过赋予int或Integer类型(通过自动装箱)参数来调用——别的都不行。长远来看,一个只需要对象提供to_i方法(返回一个整型、FixNum或者BigNum都可)的方法会更加灵活。开发者无法控制的类可能不会继承自需要的类,不过它们仍然可能支持同一个协议。

介于动态(Duck Typing)和静态(接口,Scala的结构化类型)之间的,是Erlang的Behaviors。Erlang的模块(可以对比Ruby的Module)将功能分组在一起。一个模块可以使用-behaviour(gen_server).来表明它实现了gen_server Behavior,也就是说它输出了一系列特定的函数。Erlang编译器支持该用法,当模块没有输出Behavior需要的全部函数时,编译器会发出警告信号。

这也说明,不仅仅是那些提供类、对象、继承等等概念,将自己定义为“OOP”的编程语言,才适用协议的一般原理 。

你是如何记录在Ruby中对协议的使用的?即,你如何确定有互动关系的对象彼此同意并传递的一系列消息?

查看英文原文:Duck Typing and Protocols vs. Inheritance
译者简介:郑柯,目前任职《程序员》杂志社高级编辑,有志于在中国的软件开发业界推广Agile的理念和方法论,笃信以人为本,关注人,关注敏捷,关注Ruby。 参与InfoQ中文站内容建设,请邮件至china-editorial@infoq.com

译者 郑柯 InfoQ中文站总编。做过开发,当过PM,干过销售,搞过市场,最终还是回到媒体。实用的理想主义者,相信:每天改变一点点,这个世界会更好。

另一个办法 发表人 Chen Jerome 发表于
  1. 返回顶部

    另一个办法

    发表人 Chen Jerome

    不知道异常的开销如何,不然可以用检查是否raise NoMethodError来检查是否支持某方法。

    [Ruby中文社区] - www.ruby-lang.org.cn

深度内容

大规模视频网站的计费与流量管理

本次分享将会就大规模视频网站的计费与流量管理这个话题,从操作层面细细进行讲解和分析,为系统工程师们揭示平日里我们没有关心的另一些内容。同时也希望本次分享能揭示行业中的一些“潜规则”,让互联网行业的流量与带宽管理更为开放与简洁。
本次演讲视频录制于QCon杭州2011

专访Jeffrey Richter:Windows 8是微软的重中之重

Jeffrey Richter以其多本Windows核心技术的经典著作而闻名,同时,他深入掌握微软的.NET等一系列核心技术,2012年1月,Jeffrey Richter在北京接受了InfoQ中文站的专访,谈到Windows 8和WinRT编程,并就异步编程、Windows编程中的可扩展性、性能和安全性方面给出自己的建议。

应用云平台的可用性——从新浪SAE看云平台设计

云计算平台的可用性,相比传统互联网服务而言,更加复杂和困难,也更具有挑战性。本文借助新浪SAE云平台为读者讲述了云平台可用性的定义、如何打造高可用的平台,以及对云计算的用户提出了建议。

JVM定制改进 @ 淘宝

淘宝高度重视Java平台的健康发展,组建了一个团队专注于Java平台的底层部分的性能、功能与稳定性改进;工作主要基于OpenJDK中的HotSpot VM开展,其中一些通用的功能随后也会逐渐反馈给OpenJDK社区。希望能与使用Java平台开发应用的大家交流经验。
本次演讲视频录制于QCon杭州2011

"伤得起"的云计算应用——对云端应用之架构的思考

2011年4月21日至22日是值得云计算从业者纪念的日子。Amazon的IaaS服务出现故障,导致许多商业网站的服务中断,影响非常严重。作为云计算用户,我们需要思考的是,如何保证即便在云服务不可用的情况,我们的应用架构仍然能够屹立不倒?本文正是站在云计算用户的角度试图探讨这一问题。

让交付的速度跟上思考的速度

12人的技术团队,4组刀片服务器,每月20亿的访问量,每日1次准时部署,99.9%的可用性。这可能吗?当然。想知道如何做的吗?百姓网将与您分享他们在DevOps实践过程中的经验和技巧。
本次演讲视频录制于QCon杭州2011

架构之路——穿行在产品和业务之间

篱笆作为一家起源于社区的电子商务公司,反映到技术层面就是同时要面对产品和业务,以及经营战略的变化调整。如何在产品和业务的夹缝之间完成技术架构的抽象与平衡,寻找更有效的价值定位,这当中有些经验教训和个人感悟愿与众人分享。
本次演讲视频录制于QCon杭州2011

特性注入:成功三部曲

本文将对特性注入以及相关方法做一个扫盲性的介绍。我们会解释这个框架的关键要素,并附上实例来证实它们。为了让文章保持相对较短,我们不会深入到某个工具或方法中,而是会给出一些参考资料,以便大家做进一步的研究。