消息传递和方法调用有什么区别?

41

消息传递和方法调用之间有区别吗,还是可以视为等价?这可能具体取决于语言;许多语言不支持消息传递(尽管我能想到的所有语言都支持方法),而支持消息传递的语言可以有完全不同的实现。此外,根据语言(C vs. Java vs Lisp vs 您喜欢的语言),方法调用存在很大的差异。我相信这是与语言无关的。在您喜欢的语言中,通过传递的方法与调用的方法相比,您可以做些什么,反之亦然?


1
你的意思是“你可以用传递的消息做什么,而不能做...”吗?如果是这样,我会为您进行更正。 - Bevan
面向对象编程中的原始概念是让“消息”在对象之间传递。如果您想让 Object2 执行某些操作,您将传递一个消息给它,告诉它“执行 SomeAction”。在编程语言中,其语法变成了 Object2.SomeAction(...);然后被称为“调用方法”,而不是“传递消息”。几乎所有语言中的一个缺点是,Object2.SomeAction(...) 既是同步的,而且几乎总是限于同一线程或进程。 - Ian Boyd
5个回答

12

以 Objective-C 作为消息传递的例子,以 Java 作为方法。两者最大的区别在于,当你传递消息时,对象决定如何处理这个消息(通常导致对象中的一个实例方法被调用)。

然而,在 Java 中,方法调用是更加静态的,因为你必须拥有一个对应类型的对象引用,并且该类型中必须存在具有相同名称和类型签名的方法,否则编译器会报错。有趣的是,实际调用是动态的,尽管程序员不一定能看出来。

例如,考虑这样一个类:

class MyClass {
    void doSomething() {}
}

class AnotherClass {
    void someMethod() {
        Object object = new Object();
        object.doSomething(); // compiler checks and complains that Object contains no such method.

        // However, through an explicit cast, you can calm the compiler down,
        // even though your program will crash at runtime
        ((MyClass) object).doSomething(); // syntactically valid, yet incorrect
    }
}

然而,在Objective-C中,编译器仅对向对象发送消息并认为该对象可能无法理解时发出警告,但忽略它不会阻止程序执行。

虽然这很强大和灵活,但如果使用不当会导致难以找到的bug,因为堆栈上的损坏。

摘自这里的文章。另请参见文章获取更多信息。


10
作为第一近似解,答案是:在“正常行为”的情况下完全没有问题。
尽管许多人认为技术上有区别,但通常情况下都是相同的:缓存查找要执行特定命名操作的代码片段(至少对于普通情况而言)。将操作名称称为“消息”或“虚拟方法”并没有什么区别。
但是:Actor语言真的很不同:具有活动对象(每个对象都具有隐式消息队列和工作线程 - 至少在概念上),处理并行处理变得更加容易(请Google搜索“通信顺序进程”以获取更多信息)。
但是:在Smalltalk中,可以包装对象以使它们类似于演员,而不必实际更改编译器、语法甚至重新编译。
但是:在Smalltalk中,当您尝试发送接收方无法理解的消息时(即“someObject foo:arg”),会创建一个包含名称和参数的消息对象,并将该消息对象作为参数传递给“doesNotUnderstand”消息。因此,对象可以自行决定如何处理未实现的消息发送(也称为调用未实现的方法)。它当然可以将它们推入工作流程队列来顺序化它们……
当然,在静态类型语言中这是不可能的(除非您非常重视反射),但实际上是非常有用的功能。代理对象、按需加载代码、远程过程调用、学习和自修改代码、适应和自优化程序、Corba和DCOM包装器、工作队列都建立在该方案之上。它可能会被滥用,并导致运行时错误 - 当然。
所以说这是一把双刃剑。锋利而强大,但在初学者手中会非常危险。
编辑:我在这里写的是语言实现(如Java vs. Smalltalk - 而不是进程间机制)。

8
据我所知,它们已被正式证明是等价的。至少可以忽略调用地址与内存中实际位置的直接等价性,并将其仅视为一个数字。从这个角度来看,该数字仅是一个抽象标识符,可唯一标识您希望调用的特定功能类型。
即使在同一台机器上调用函数时,也没有真正要求调用地址直接指定被调用函数的物理(甚至虚拟)地址。例如,尽管几乎没有人真正使用它们,但Intel保护模式任务门允许直接调用任务门本身。在这种情况下,只有地址的段部分被视为实际地址--即,对任务门段的任何调用最终都会调用相同的地址,而不管指定的偏移量如何。如果需要,处理代码可以检查指定的偏移量,并使用它来决定要调用的单个方法--但指定的偏移量和调用的函数地址之间的关系可以完全是任意的。
成员函数调用只是一种消息传递类型,它提供(或至少促进)在涉及服务的客户端和服务器共享通用地址空间的常见情况下的优化。抽象服务标识符与提供该服务的地址之间的1:1对应关系允许从一个到另一个的微不足道,异常快速的映射。
与此同时,请不要误解:某些东西看起来像成员函数调用并不会阻止它实际上在另一台机器上或异步执行,或者(通常)两者都有。完成此操作的典型机制是代理函数,将成员函数调用的“虚拟消息”转换为可以(例如)根据需要通过网络传输的“真实消息”(例如,Microsoft的DCOM和CORBA都经常这样做)。

7
实际上,它们在实践中并不相同。消息传递是在两个或多个并行进程之间传输数据和指令的一种方式。方法调用是调用子例程的一种方式。Erlang的并发性是基于前者的概念构建的,采用了并发面向编程。
消息传递很可能涉及某种形式的方法调用,但方法调用并不一定涉及消息传递。如果涉及,则为消息传递。消息传递是执行两个并行进程之间同步的一种形式。方法调用通常意味着同步活动。调用者在方法完成之前必须等待才能继续。消息传递是协程的一种形式。方法调用是子例程的一种形式。
所有子例程都是协程,但并非所有协程都是子例程。

3
"Message passing" 也可以指像 Smalltalk、Objective-C 和 Ruby 这样的语言中调用方法的方式。 - mipadi
2
是的,这些编程语言喜欢将其视为消息传递,但它实际上是一个子例程,涉及将某些内容推送到堆栈上,同时暂停调用子例程的方法。实现是堆栈传递方法调用,这意味着调用者在等待调用例程返回时被阻塞。消息传递没有这样的语义。我的意思是,它不必等待它调用的例程完成。这就是区别。 - chubbsondubs
1
在我看来,楼主并没有真正讨论进程间通信。 - Dave Newton
我也不是。我并没有特别谈论IPC。我使用进程的通用方式来表示在操作系统进程中运行的任何任务。但是在一个进程中,你仍然需要在并行任务之间传递信息。恰巧这两种方法也可以用于IPC,但它并不严格属于IPC。 - chubbsondubs

3

消息传递和方法调用有区别吗,还是可以视为等效的?

它们很相似。一些不同之处:

消息可以同步或异步传递(例如在Windows中SendMessage和PostMessage之间的区别)

您可能会发送一条消息而不知道要发送到哪个远程对象

目标对象可能在远程计算机或操作系统上。


抱歉挖掘了一个很久以前的答案,但是关于“目标对象可能在远程机器或操作系统上”的问题-这是否真正独特于消息传递或方法调用?如果我正确理解了您的暗示,您是在陈述这是消息传递的一个属性。如果是这样,那么Java中的远程方法调用不是与此相矛盾吗? - obfuscation

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接