鸭子接口

2005 年 12 月 21 日

也许我太天真了,但我从未料到我关于 HumaneInterface 的文章会引发如此多的讨论。遗憾的是,大部分讨论都变成了关于 Ruby 的 Array 和 Java 的 List 的优劣之争,而不是我试图阐述的根本问题,但尽管如此,我认为也出现了一些不错的对话分支。

(虽然我感觉我应该指出,我并没有打算说我认为 Ruby 的 Array 更好,或者说 Ruby 更好 - 我认为两者都不比另一个更好,除非你给出更多上下文。事实证明,我喜欢 Ruby 的 Array 和 Java 的 List,尽管它们的设计方式截然不同。就像任何软件一样,它们都有缺陷,因为没有人会编写出完全符合我此刻需求的类,但我不会想让我的任何代码与它们竞争。关键是它们都有用,我经常使用它们 - 这就是它们出现在我脑海中作为示例的原因。)

这些对话线程中的一条指出,Array 和 List 之间的差异除了人性化/最小化哲学之外,还有其他原因。其中一个原因与类似功能在两种语言中扮演的不同角色有关。

Ruby 的 Array 有许多方法,当人们查看列表时,这些方法让一些人感到困惑。Push 和 pop - 正如 Elliotte 所说,“有人将一个堆栈推入了列表”。还有 shift 和 unshift,它们就像一个列表。这看起来不对 - Elliotte 再次说道,“如果你使用的是队列或堆栈,你不应该使用 Queue 或 Stack 类,而是 List 类吗?”

阅读这些内容触发了我记忆深处的一个想法。我翻出了 Smalltalk 最佳实践模式,这是一本很棒的书,任何认真想要理解面向对象的人(尽管语法很奇怪)都应该阅读。

“许多人刚接触 Smalltalk 时编写的第一个对象之一就是 Stack。Stack 是基本的数据结构,在关于理论编程语言的歌曲、故事和数百篇论文中都有记载……在任何基本映像中都没有 Stack 类。我见过很多人写过 Stack 类,但它们似乎从未持续很长时间。”

-- 肯特·贝克

Smalltalk 的方法,至少在当时,是使用 OrderedCollection,它是 Smalltalk 中 Ruby 的 Array 的等效类。甚至没有 push 或 pop - 相反,肯特展示了如何使用 addLast:removeLast:

肯特没有解释为什么没有堆栈(和队列) - “为什么 Smalltalk 中没有 Stack?好吧,‘仅仅因为’。使用 OrderedCollection 模拟堆栈是 Smalltalk 文化的一部分。”

我不确定我对这件事有什么感觉(我从肯特的写作中明显感觉到不确定性)。如果你要使用类似队列的东西,使用 Stack new 而不是 OrderedCollection new 确实有意义,更不用说使用 pushpop 而不是 addLastremoveLast 了。

我发现,这种情况的一部分可能与静态语言和动态语言之间的差异有关。静态语言喜欢通过严格的类型接口与对象进行通信,动态语言的类可以适应多种角色 - 鸭子类型。Java 也有一个列表,它兼作队列:LinkedList,但你通常会通过不同的接口使用它,而没有意识到共同的实现。Smalltalk 的感觉是我们可以用 OrderedCollection 获得我们想要的东西,那么为什么要构建另一个类呢?Ruby 似乎在回应这种反应,并为在该上下文中使用它的人添加了有意义的方法名。(虽然公平地说,我不知道 Ruby 程序员是否真的对 Array 作为堆栈感到满意,或者它是否是一个令人遗憾的遗留问题。)

另一个因素是语言鼓励如何实现这些结构。正如 查尔斯·米勒 所说,“Java 的设计允许使用小型接口,以及作为辅助类上的静态方法提供的实用程序函数。Ruby 的设计允许使用具有混合实用程序方法的更大类。”

也许从这里得出的结论之一是,我们应该谨慎地避免使用一种语言的类特征来评判另一种语言的价值观。Array 是否等同于 List,或者等同于 List 加上集合包中的各种接口和实现 - 或者它是否更复杂?有些人可能会对对列表进行 78 种操作的想法感到反感,但我怀疑 Lisp 程序员会想到更多要添加的操作。Ruby 的 Array 有它的缺点,但我必须承认,我更喜欢使用它而不是 Java 集合,尽管这究竟是由于人性化接口指南还是由于 Ruby 的语法支持,我不确定。

总而言之,我不确定我同意谁的观点,或者这是否真的那么重要。就像这些争论中的许多一样,我认为最有趣的事情是尝试理解双方的观点。