Zak*_*aki 24 language-agnostic oop
在几篇关于面向对象编程的介绍性文章中,我遇到了上述陈述.
来自维基百科,"在OOP中,每个对象都能够接收消息,处理数据和向其他对象发送消息,并且可以被视为具有独特角色或责任的独立'机器'."
该语句在代码中的含义到底是什么意思?
class A
{
methodA()
{
}
}
class B
{
methodB()
{
}
}
class C
{
main()
{
A a=new A();
B b=new B();
a.methodA(); // does this mean msgs passing??
b.methodB(); // or does this?? I may be completely off-track here..
}
}
Run Code Online (Sandbox Code Playgroud)
Wil*_*cat 24
如果我们谈论的是OOP,那么"消息传递"一词来自Smalltalk.简而言之,Smalltalk的基本原则是:
如果您对Smalltalk感兴趣,请查看Pharo或Squeak.
Java/C#/ C++和许多其他语言使用的方法略有不同,可能来自Simula.您调用方法而不是传递消息.
我认为这些术语或多或少相同.可能唯一有趣的区别是消息传递(至少在Smalltalk中)总是依赖于动态调度和后期绑定,而在方法调用的情况下,也可以使用静态调度和早期绑定.例如,C++(AFAIK)默认执行早期绑定,直到"虚拟"关键字出现在某处...
无论如何,无论你的编程语言用于两个对象之间的通信(消息传递或方法调用),它都被认为是一种很好的OOP风格,禁止直接访问Smalltalk术语中的实例变量或C++术语中的数据成员或任何术语.用于您的编程语言.
Smalltalk直接禁止在语法级别访问实例变量.正如我上面提到的,Smalltalk程序中的对象只能通过传递/接收消息进行交互.许多其他语言允许在语法级别访问实例变量,但这被认为是一种不好的做法.例如,着名的Effective C++书籍包含相应的建议:第22项:声明数据成员私有.
原因是:
最后一个是最重要的.这是封装的本质- 信息隐藏在类级别.
关于封装的观点比最初出现的更重要.如果从客户端隐藏数据成员(即封装它们),则可以确保始终维护类不变量,因为只有成员函数才能影响它们.此外,您保留以后更改实施决策的权利.如果你不隐藏这样的决定,你很快就会发现,即使你拥有一个类的源代码,你更改公共内容的能力也受到极大限制,因为太多的客户端代码将被破坏.公共意味着未被封装,实际上,未封装意味着不可更改,特别是对于广泛使用的类.然而,广泛使用的类最需要封装,因为它们最能从使用更好的实现替换一个实现的能力中受益.
(с)Scott Meyers,Effective C++:55改进程序和设计的具体方法(第3版)
ewe*_*nli 10
不完全是你的问题的答案,但有一点关于消息调度与方法调用的离题:
术语消息指的是您不知道由于多态而将调用哪个方法的事实.你要求一个对象做某事(因此称为消息一词),并相应地采取行动.术语方法调用具有误导性,因为它建议您选择一个精确的方法.
术语消息也更接近动态语言的实际情况,您可以实际发送对象不理解的消息(参见doesNotUnderstandSmalltalk).如果没有匹配,那么你就不能真正谈论方法调用,并且消息调度将失败.在静态类型语言中,可以防止此问题.
"传递信息"是一种抽象.
今天大多数OO语言以特征调用的形式实现该抽象.功能我的意思是一种方法,一种操作(见下面的编辑),属性或类似的东西.OOSC2中的 Bertrand Meyer 认为特征调用是现代OO语言中的基本计算单位; 这是一种完全有效且连贯的方式来实现"对象通过消息传递进行通信"的旧抽象概念.
其他实现技术也是可能的.例如,由某些中间件系统管理的对象通过队列工具传递消息进行通信.
总结:不要将抽象与代码混淆.在过去,程序员过去常常关心理论,因为编程几乎不存在于主流职业中.今天,大多数程序员很少熟悉代码背后的理论.:-)
顺便说一句,对于理论倾向,面向代理的建模和编程方法通常强调消息传递作为代理之间的通信机制,认为它源于言语行为理论.这里不涉及方法调用.
编辑.在大多数OO语言中,您调用操作而不是方法.它是运行时引擎,它决定调用哪个特定方法作为对您调用的操作的响应.这使得能够实现经常提到的多态性机制.当我们提到"调用方法"时,这种细微差别在常规用语中通常会被遗忘.但是,为了区分操作(作为实现消息传递的特征)和方法(作为所述操作的特定版本),有必要.
它们指的是客户端可以在接收对象上调用方法,并将数据传递给该对象,但该对象可以自主决定如何处理该数据,并根据需要维护自己的状态.
客户端对象无法直接操作接收对象的状态.这是封装的一个优点- 接收对象可以独立地强制执行自己的状态并更改其实现,而不会影响客户端与其交互的方式.
在 OOP 中,对象不一定通过传递消息来相互通信。他们以某种方式相互通信,允许他们指定他们想要做什么,但将该行为的实现留给接收对象。传递消息是实现接口与实现分离的一种方式。另一种方法是在接收对象中调用(虚拟)方法。
至于您的哪些成员函数调用真正符合这些要求,在与语言无关的基础上很难说。举个例子,在 Java 中,成员函数默认是虚拟的,所以你对a.methodA()和的调用b.methodB()相当于传递一条消息。您(尝试 a)调用b.methodA()并且a.methodB()不会编译,因为 Java 是静态类型的。
相反,在 C++ 中,成员函数默认不是虚拟的,因此您的任何调用都不等同于消息传递。要获得等效的东西,您需要将至少一个成员函数显式声明为虚拟函数:
class A {
virtual void methodA() {}
};
Run Code Online (Sandbox Code Playgroud)
然而,就目前而言,这基本上是一种“没有区别的区别”。要了解这意味着什么,您需要使用一些继承:
struct base {
void methodA() { std::cout << "base::methodA\n"; }
virtual void methodB() { std::cout << "base::methodB\n"; }
};
struct derived {
void methodA() { std::cout << "derived::methodA\n"; }
virtual void methodB() { std::cout << "derived::methodB"; }
};
int main() {
base1 *b1 = new base;
base2 *b2 = new derived;
b1->methodA(); // "base::methodA"
b1->methodB(); // "base::methodB"
b2->methodA(); // "base::methodA"
b2->methodB(); // "derived::methodB"
return 0;
}
Run Code Online (Sandbox Code Playgroud)