在我看来,消息传递的OO范式是解决两个不同问题的解决方案。我将在下面的伪代码中详细解释我的意思。
(1) 它解决了分派问题:
=== 在文件animal.code中 ===
- Animals can "bark"
- Dogs "bark" by printing "woof" to the screen.
- Cats "bark" by printing "meow" to the screen.
=== 在文件myprogram.code中 ===
import animal.code
for each animal a in list-of-animals :
a.bark()
在这个问题中,“bark”是一种带有多个“分支”的方法,它根据参数类型的不同而有所不同。我们为我们感兴趣的每种参数类型(狗和猫)实现“bark”。运行时,我们能够遍历动物列表并动态选择适当的分支。
(2)它解决了命名空间问题:
=== 在 animal.code 文件中 ===
- Animals can "bark"
=== 在文件树.code中 ===
- Trees have "bark"
=== 在 myprogram.code 文件中 ===
import animal.code
import tree.code
a = new-dog()
a.bark() //Make the dog bark
…
t = new-tree()
b = t.bark() //Retrieve the bark from the tree
在这个问题中,"bark" 实际上是两个概念上不同的函数,它们恰好有相同的名称。参数类型(狗或树)决定了我们实际指的是哪个函数。
多方法优雅地解决了问题1。但我不理解它们如何解决问题2。例如,上面两个例子中的第一个可以直接转换为多方法:
(1)使用多方法的狗和猫
=== 在 animal.code 文件中 ===
- define generic function bark(Animal a)
- define method bark(Dog d) : print("woof")
- define method bark(Cat c) : print("meow")
=== 在文件 myprogram.code 中 ===
import animal.code
for each animal a in list-of-animals :
bark(a)
重点是,方法bark(Dog)在概念上与bark(Cat)相关。第二个例子没有这个属性,这就是我不理解多方法如何解决命名空间问题的原因。
为什么多方法对动物和树不起作用?===在文件animal.code中===
- define generic function bark(Animal a)
=== 在文件树.code中 ===
- define generic function bark(Tree t)
=== 在文件myprogram.code中 ===
import animal.code
import tree.code
a = new-dog()
bark(a) /// Which bark function are we calling?
t = new-tree
bark(t) /// Which bark function are we calling?
在这种情况下,通用函数应该在哪里定义?它应该定义在顶层,在animal和tree之上吗?将动物和树的叫声视为同一通用函数的两个方法是不合理的,因为这两个函数在概念上是不同的。
据我所知,我还没有找到任何解决此问题的先前工作。我查看了Clojure多方法和CLOS多方法,它们都有相同的问题。我抱着希望能够解决这个问题或者通过令人信服的论点证明这实际上并不是一个真正的问题。
如果需要澄清问题,请告诉我。我认为这是一个相当微妙(但重要)的问题。
感谢sanity、Rainer、Marcin和Matthias的回复。我理解了你们的回答,并完全同意动态分派和命名空间解析是两个不同的概念。CLOS没有混淆这两个概念,而传统的消息传递OO则是如此。这也允许直接扩展到多继承的多方法。
我的问题具体涉及当混淆是“可取”的情况。以下是我想表达的示例。
=== 文件:XYZ.code ===
define class XYZ :
define get-x ()
define get-y ()
define get-z ()
=== file: POINT.code ===
define class POINT :
define get-x ()
define get-y ()
=== file: GENE.code ===
define class GENE :
define get-x ()
define get-xx ()
define get-y ()
define get-xy ()
==== 文件:my_program.code ===
import XYZ.code
import POINT.code
import GENE.code
obj = new-xyz()
obj.get-x()
pt = new-point()
pt.get-x()
gene = new-point()
gene.get-x()
由于命名空间解析与调度混淆,程序员可以在所有三个对象上天真地调用get-x()。这也是完全明确的。每个对象都“拥有”自己的一组方法,因此程序员的意图不会产生混淆。
相比之下,多方法版本如下:
=== 文件:XYZ.code ===
define generic function get-x (XYZ)
define generic function get-y (XYZ)
define generic function get-z (XYZ)
=== file: POINT.code ===
define generic function get-x (POINT)
define generic function get-y (POINT)
=== 文件:GENE.code ===
define generic function get-x (GENE)
define generic function get-xx (GENE)
define generic function get-y (GENE)
define generic function get-xy (GENE)
==== file: my_program.code ===
import XYZ.code
import POINT.code
import GENE.code
obj = new-xyz()
XYZ:get-x(obj)
pt = new-point()
POINT:get-x(pt)
gene = new-point()
GENE:get-x(gene)
由于XYZ的get-x()与GENE的get-x()没有概念关系,因此它们被实现为单独的通用函数。因此,最终的程序员(在my_program.code中)必须明确限定get-x()并告诉系统他到底要调用哪个get-x()。
虽然这种显式的方法更清晰,而且容易推广到多分派和多继承,但是滥用分派来解决命名空间问题是面向对象消息传递中非常方便的特性。
我个人认为,我的代码有98%可以使用单分派和单继承来充分表达。我更多地使用分派来解决命名空间问题,而不是使用多分派,因此我不愿放弃它。
有没有一种方法可以让我在多方法设置中避免需要显式限定函数调用呢?
似乎共识是:
- 多方法解决了分派问题,但没有解决命名空间问题。
- 在概念上不同的函数应该具有不同的名称,并且用户应该手动限定它们。
这听起来像是一个开放性的研究问题。如果一种语言提供了一种既可以用于多方法,又可以用于命名空间解析的机制,那么这是否是一个理想的功能呢?
我喜欢通用函数的概念,但目前感觉它们被优化为在使“非常困难的事情变得不那么困难”的同时,牺牲了使“琐碎的事情稍微有点烦人”的代价。由于大多数代码都是琐碎的,所以我仍然认为这是一个值得解决的问题。