写点什么

Duck Typing 与协议 vs. 继承

2007 年 12 月 05 日

在 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 编程语言的使用者们在很久以前就已经很明白了。 Smalltalk ObjectiveC ,作为动态 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) = <br></br>{<br></br> element.setText(text.trim()<br></br> .replaceAll("\n","")<br></br> .replaceAll("\t","")) <br></br>} <br></br>此函数需要 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

2007 年 12 月 05 日 18:14552
用户头像

发布了 479 篇内容, 共 128.0 次阅读, 收获喜欢 30 次。

关注

评论

发布
暂无评论
发现更多内容

Week 03 学习总结

卧石漾溪

极客大学架构师训练营

Tweak原理与越狱防护

大冯宇宙

万恶的NPE差点让我半个月工资没了

java金融

Java 程序员 互联网 NPE 空指针

week 3学习总结

Geek_2e7dd7

Week3总结+作业

林毋梦

极客大学架构师训练营

架构师训练营 Week 03 总结

Wancho

【week03】总结

chengjing

元年云“宽能力”拓宽成长型企业数字化升级之路

人称T客

week3 学习总结

不在调上

极客大学架构师训练营

【week03】作业1

chengjing

Week 03- 作业二:学习总结

dean

极客大学架构师训练营

【第三周】学习总结——Flower框架学习和设计模式

三尾鱼

极客大学架构师训练营

架构师训练营第三周课后作业

竹森先生

极客大学架构师训练营

week3

Geek_2e7dd7

Java HashMap loadfactor没有必要非是0.75

i风语

Java redis hashmap loadfactor hash

【架构师训练营 - 作业 -3】组合模式

小动物

极客大学架构师训练营 作业 第三周

03周作业——设计模式

dao

设计模式 极客大学架构师训练营 作业

第三周总结

andy

架构师训练营第 3 周 _ 学习总结

方舟勇士

课程总结

week3 作业& 手撕单例模式

不在调上

架构师训练营Week03

Frank Zeng

架构师训练营week3学习总结

Frank Zeng

代码重构--架构师必备技能

李广富

【架构师训练营 - 周总结 -3】设计模式、重构

小动物

总结 极客大学架构师训练营 第三周

第三周作业

andy

大白话Java多线程,小白都能看的懂的哦

java金融

Java 多线程 线程安全 创建线程方式 什么是多线程

Week 03- 作业一:设计模式

dean

极客大学架构师训练营

「架构师训练营」第 3 周作业 - 模式与重构

guoguo 👻

极客大学架构师训练营

从单机事务到分布式事务

ElvinYang

代码重构练习三

李广富

week 3

陈皮

「中国技术开放日·长沙站」现场直播

「中国技术开放日·长沙站」现场直播

Duck Typing与协议 vs. 继承-InfoQ