Smalltalk和Simula的面向对象编程(OOP)有哪些哲学差异?
这是一个间接涉及Java和C#与C++之间关系的问题。据我了解,C++基于Simula,而Java和C#基本上来自Smalltalk家族。
在更广泛的面向对象编程中,存在几个关键的“风格”差异。
在所有情况下,关于静态类型系统或动态类型系统的陈述主要意味着其中之一,但问题远非明确或清晰定义。 此外,许多语言选择模糊这些选择之间的界限,因此这不是任何二元选择的列表。
或者说,“foo.Bar(x)
是什么意思?”
1经常在静态类型框架中使用,如果在编译时检查到不存在这样的实现,则会出现错误。此外,这些语言通常区分Bar(x)和Bar(y)如果x和y是不同的类型。这是方法重载,具有相同名称的结果方法被视为完全不同。
2经常在动态语言中使用(这些语言倾向于避免方法重载),因此,在运行时,foo的类型可能没有针对名为“Bar”的消息的“处理程序”,不同的语言以不同的方式处理这种情况。
如果需要,两者都可以以相同的方式在后台实现(通常第二个Smalltalk样式的默认值是调用函数,但并非在所有情况下都定义了这种行为)。 由于前一种方法经常可以轻松实现为简单的指针偏移函数调用,因此它可以更容易地相对较快地实现。这并不意味着其他风格不能也快速实现,但在这样做时可能需要更多的工作,以确保不会损害更大的灵活性。
或“婴儿从哪里来?”
再次强调,1倾向于发生在静态语言中,2倾向于动态语言,尽管这不是必要条件,它们只是适合这种风格。
或“是什么还是怎么做?”
这绝非二元选择。大多数基于类的语言允许抽象方法的概念(尚未实现的方法)。如果您有一个所有方法都是抽象的类(在C++中称为纯虚拟),那么该类的总量基本上就是一个接口,尽管可能还定义了一些状态(字段)。真正的接口不应该有状态(因为它仅定义了可能性,而不是如何发生)。
只有较旧的面向对象编程语言倾向于仅依赖其中之一。
VB6仅具有接口,并且没有实现继承。
Simula允许您声明纯虚拟类,但您可以实例化它们(在使用时会出现运行时错误)。
或者说"谁是父亲?"
这个问题引发了相当大的争议,尤其是它是区分 C++ 的 OOP 实现和被视为可能的继任者的许多现代静态类型语言之间的关键因素,如 c# 和 java。
或者说,“你想对我做什么?”
或者说"你认为你是谁?"
语言设计中更普遍的方面之一,本文不再讨论,但这个决策中的选择影响了OOP的许多方面,就像前面提到的那样。
多态后期绑定的各个方面可能取决于:
语言越动态,这些决策就越复杂,但相反地,语言用户而不是语言设计者对决策有更多的输入。 在这里举例子有些愚蠢,因为静态类型的语言可以被修改以包括动态方面(例如c# 4.0)。
我认为Java和C#也可以归类为Simula编程语言:
Smalltalk是动态类型的,与你提到的其他四种语言有很大不同。
Smalltalk是结构类型(又称鸭子类型),而其他四种语言是名义类型。
(Java和C#与Smalltalk的共同之处在于它们主要基于虚拟机,但对编程风格的影响很小)。
Java和C#绝对不是Smalltalk家族的成员。Alan Kay甚至说,在他创造面向对象编程时,并没有像Java或C++那样的东西在他的脑海中。Java、C#和C++几乎以相同的方式解释面向对象编程。
像Smalltalk和Ruby这样的语言具有基于消息传递的根本不同的模型。在C++中,类本质上是方法和状态的命名空间。方法调用在编译时绑定。而在Smalltalk中,“方法调用”直到运行时才绑定。这导致在C++中
foo->bar
这句话的意思是"在foo对象上调用bar方法"。如果bar方法不是虚函数,我想地址会被明确引用。
在Smalltalk中
foo bar
foo
可以对该消息进行任何操作。默认行为是调用名为bar
的方法,但这不是必需的。Ruby利用此属性进行ActiveRecord列访问器。当您拥有一个ActiveRecord对象并向其发送数据库表中某个列的名称作为消息时,如果没有定义该名称的方法,则会检查该表上是否有该名称的列,如果有,则返回值。Eiffel是一种静态类型、编译型、多继承的纯面向对象编程语言。
在现代(这里的“现代”用词不当)面向对象编程语言中,Objective C 最像 Smalltalk。
在 C++、C# 和 Java 中:消息在编译时绑定。
你可以把方法调用看作是发送给对象的一条消息。
在 Objective C 和 Smalltalk 中:消息在运行时绑定。
我认为,基于类的面向对象编程(Smalltalk、Simula、C#和Java都是这种方式的例子)与基于原型的面向对象编程(Self起源并在JavaScript中最为广泛)在概念上存在相当大的差异。
// C++
class Base {
void doSomething() {
cout << "Base::doSomething() called!\n";
}
}
class Derived : Base {
void doSomething() {
cout << "Derived::doSomething() called!\n";
}
}
int main() {
Base* b = new Base();
Derived* d = new Derived();
b->doSomething(); // prints "Base::doSomething() called!"
d->doSomething(); // prints "Derived::doSomething() called!"
Base* d2 = d; // OK; Liskov substitution principle.
d2->doSomething(); // prints "Base::doSomething called!" (!)
delete b;
delete d;
return 0;
}
VS:
// Objective-C
//Base.h
@interface Base
{
}
-(void)doSomething
@end
//Base.m
#import "Base.h"
@implementation Base
-(void) doSomething {
printf("doSomething sent to Base!");
}
@end
//Derived.h
#import "Base.h"
#import "Base.m"
@interface Derived : Base
{
}
@end
//Derived.m
#import "Derived.h"
@implementation Derived
-(void) doSomething {
printf("doSomething sent to Derived!")
}
@end
//Main.m
#import "Base.h"
#import "Base.m"
#import "Derived.h"
#import "Derived.m"
int main() {
Base* b = [[Base alloc] init];
Derived* d = [[Derived alloc] init];
[b doSomething]; // prints "doSomething sent to Base!"
[d doSomething]; // prints "doSomething sent to Derived!"
Base* d2 = d;
[d2 doSomething]; // prints "doSomething sent to Derived!"
[b release];
[d release];
return 0;
}