OOP的学校是什么?

Smalltalk OOP和Simula OOP之间是否存在哲学差异?

这是一个间接涉及Java&C#vs C ++的问题。 据我所知,C ++基于Simula,但Java和C#或多或少来自Smalltalk系列。

在更广泛的OOP横幅中,’Style’的几个关键’差异’。

在所有情况下,关于静态动态类型系统的陈述主要是指一个或另一个,问题远非明确或明确定义。 此外,许多语言选择模糊选择之间的界限,因此这不是任何方式的二元选择列表。

多态晚期结合

或者“ foo.Bar(x)是什么意思?”

  1. 类型的层次结构被平铺到每个实例的特定实现(通常通过vtable完成),并且通常允许显式引用基类实现。
    • 从概念上讲,您会看到foo在呼叫站点上最具体的类型。 如果它有一个被调用的参数x的Bar实现,如果不是,则选择foo的父级并重复该过程。
    • 示例:C ++ / Java / C#,经常使用“ Simula样式 ”。
  2. 纯粹的消息传递。 foo中处理消息’named’“Bar”的代码被要求接受x。 只有名称很重要,而不是呼叫网站可能对Bar究竟是什么意图所做的任何假设。 与之前的样式相比,其中所讨论的方法是Bar,已知在编译时定义的类型层次结构的任何已知内容(尽管层次结构中的精确位置保留到运行时)。
    • 示例:Objective-C / Ruby,经常使用“ Smalltalk样式 ”。

1通常在静态类型框架中使用,在这种框架中它是一个错误,在编译时检查是否存在这样的实现。 此外,如果x和y是不同类型,则语言通常区分Bar(x)和Bar(y)。 这是方法重载,并且具有相同名称的结果方法被视为完全不同。

2通常用在动态语言中(这往往会避免方法重载),因为在运行时foo的类型对于名为“Bar”的消息没有“处理程序”,不同的语言可能以不同的方式处理它。

如果需要,两者都可以在幕后以相同的方式实现(通常,第二种,Smalltalk样式的默认设置是调用一个函数,但在所有情况下都不会定义这个行为)。 由于前一种方法可以经常容易地实现为简单的指针偏移函数调用,因此可以更容易地相对快速地进行。 这并不意味着其他样式也不能快速完成,但可能需要做更多工作以确保在这样做时不会损害更大的灵活性。

inheritance/重用

或“婴儿从何而来?”

  1. 基于class级
    • 方法实现被组织成称为类的组。 当需要实现inheritance时,定义一个扩展父类的类。 通过这种方式,它可以获得父项的所有暴露方面(包括字段和方法),并且可以选择更改某些/所有这些方面但不能删除任何方面。 您可以添加和更新但不能删除。
    • 示例:C ++ / Java / C#(注意SmallTalk和Simula都使用此方法)
  2. 基于原型
    • 对象的任何实例只是已识别方法的集合(通常由名称标识)和以(再次命名)字段的forms的状态。 每当需要此“类型”的新实例时,可以使用现有实例来克隆新实例。 此新类保留上一个类的状态和方法的副本,但可以进行修改以删除,添加或更改现有的命名字段和方法。
    • 示例:Self / JavaScript

同样,1倾向于在静态语言中发生,2在动态中发生,尽管这绝不是一个要求,它们只是适合于风格。

基于接口或类

或“什么或如何?”

  1. 接口列出了所需的方法。 他们是合同
    • 示例:VB6
  2. 类列出了所需的方法,但可以选择提供它们的实现
    • 示例:Simula

这绝不是二元选择。 大多数基于类的语言允许抽象方法的概念(尚未实现的方法)。 如果你有一个所有方法都是抽象的类(在C ++中称为纯虚拟),那么这个类相当于一个接口,尽管它也可能定义了一些状态(字段)。 一个真正的接口应该没有状态(因为它只定义了可能的内容,而不是它如何发生。

只有较旧的OOP语言往往只依赖于其中一种语言。
VB6只在接口上,没有实现inheritance。
Simula允许您声明纯虚拟类,但您可以实例化它们(使用时出现运行时错误)

单个或多个inheritance

或者“谁是爸爸?”

    • 只有一种类型可以是另一种类型的父类型。 在上面基于类的表单中,您可以扩展(从实现开始)只有一种类型。 通常,这种forms包括接口的概念作为语言的第一类方面来弥补这一点。
    • 优点包括更清晰的元数据和内省,更简单的语言规则。
    • 并发症包括更难将有用的方法纳入范围(诸如MixIns和Extension方法之类的东西试图缓解这类问题)
    • 示例:C#/ java
  1. 多个 – 您可以扩展多个类
    • 优点包括某些结构更容易建模和设计
    • 并发症包括复杂的冲突解决规则,尤其是当存在可能采用父类型的重载方法时。
    • 示例:C ++ / Eiffel

这个问题引起了相当大的争论,特别是因为它是C ++的OOP实现和许多现代静态类型语言之间的关键区别,这些语言被认为是可能的inheritance者,如c#和java。

可变性

或者“你想对我做什么?”

  1. 易变的
    • 对象一旦创建就可以更改其状态。
  2. Imutable
    • 对象一旦创建就无法更改。

通常这不是全部或全部,它只是一个默认值(默认情况下,最常用的OOP语言默认为可变)。 这会对语言的结构产生很大的影响。 许多主要包含OOPfunction的函数式语言都默认对象具有不可变状态。

他们的OOP’纯粹’

或者“一切都是对象吗?”

  1. 绝对地,系统中的所有东西都被视为一个对象(甚至可能只是方法本身,它只是另一种对象,可以与其他对象相同的方式进行交互)。
    • 示例:SmallTalk
  2. 并非所有东西都是一个对象,你不能将消息传递给所有东西(虽然系统可能会跳过箍,使它看起来像你可以)
    • 示例:C ++ / C#/ Java(参见注释*)

这是相当复杂的,因为像原始的自动装箱这样的技术使它看起来像一切都是但你会发现存在几个边界情况,其中发现了这个’编译器魔法’并且在幕后发现了奥兹的谚语导致问题或错误。 在具有不变性作为默认值的语言中,这种情况不太可能发生,因为对象的关键方面(它们包含方法和状态 )意味着与对象相似但不太可能出现并发症的事物。

  • 关于Java / C#, 自动装箱 (或在c#中 )系统允许您在语法上处理任何变量,就好像它是一个对象,但实际上并非如此,并且这在诸如试图锁定自动装箱的区域中展示对象(由编译器拒绝,因为它将是一个明显的错误)。

静态或动态

或者“你认为你是谁?”

语言设计的一个更普遍的方面,而不是一个进入这里,但这个决定中固有的选择影响了前面提到的OOP的许多方面。

多态晚期绑定的各个方面可以取决于:

  • 消息传递给的对象的类型(在编译时/运行时)
  • 传递的参数类型(在编译时/运行时)

语言变得越动态,这些决策往往变得越复杂,但相反,语言用户的输入越多,而不是语言设计者在决策中的输入。 这里给出的例子将是一些愚蠢的东西,因为静态类型的语言可能被修改为包括动态方面(如c#4.0)。

我还将Java和C#放入Simula阵营:

  • Smalltalk是动态类型的,与你引用的其他四种语言完全不同。

  • Smalltalk是结构类型(别名鸭子打字),而其他四个名义上键入。

(Java和C#与Smalltalk的共同点主要是基于VM,但对编程风格的影响很小)。

Java和C#绝对不是来自Smalltalk系列。 Alan Kay甚至说,当他创建OOP时,他没有考虑Java或C ++这样的东西。 Java,C#和C ++都以完全相同的方式解释OOP。

像Smalltalk和Ruby这样的语言有一个完全不同的模型,它基于消息传递。 在C ++中,类本质上是方法和状态的名称空间。 方法调用在编译时绑定。 Smalltalk直到运行时才绑定“方法调用”。 结果是在C ++中

 foo->bar 

编译为“调用foo对象上的bar方法”。 如果bar是非虚拟的,我会想到bar方法的地址是专门引用的。

在Smalltalk

 foo bar 

表示“将消息栏发送到foo对象。” foo到达时, foo可以使用此消息执行任何操作。 默认行为是调用名为bar的方法,但这不是必需的。 此属性在Ruby中用于ActiveRecord列访问器。 如果您有一个ActiveRecord对象,并在其数据库表中将其名称作为消息发送给它,如果没有定义该名称的方法,它会检查表中是否有该列的列,如果有返回值。

消息传递可能看起来像一个微小的,无关紧要的细节,但除此之外,OOP的其余部分很容易流动。

“OOP对我来说只意味着消息传递,本地保留和保护以及状态进程的隐藏,以及所有事物的极端后期绑定。它可以在Smalltalk和LISP中完成。可能还有其他系统可以实现,但是我不知道他们。“ – Smalltalk的创始人Alan Kay

Eiffel是一种静态类型,编译,多inheritance的纯OOP语言。

http://dev.eiffel.com

现代(我使用术语轻微)OO编程语言Objective C最像smalltalk。

Messaages:

在C ++,C#和Java中:消息在编译时绑定。
您可以将方法调用视为发送到对象的消息。

在Objective C中,Smalltalk:消息在运行时绑定。

我会说静态类型和动态类型的OOP是同一OOP学校中的两个独立的学科。

Java,C#和C ++都遵循类似的OOP策略。 它基于在编译时绑定的函数调用。 根据他的调用,当编译发生时,直接函数调用或vtable中的偏移量是固定的。 相比之下,Smalltalk的OOP基于消息传递。 从概念上讲,每个方法调用都是向接收对象发出的消息,询问它是否有一个名为“Foo”的方法。

Smalltalk没有接口的概念。 它只有类似的方法。 在C ++语言组中,一切都绑定到接口。 如果不实现QueryInterface(即使它只是一个存根),也无法实现AddRef和Release,因为它们都是IUnknown接口的一部分。 在Smalltalk中,没有IUnknown。 只有3个函数的集合,其中任何一个都可以实现。

我说在概念上,在基于类的OOP(其中Smalltalk,Simula,C#和Java都是示例)和基于原型的OOP(以Self开头并且在JavaScript中最为普遍)之间存在相当大的差异。 。

除了以上几点,还有Smalltalk与Simula的概念性细分。

从概念上讲,“Smalltalk-style”通常表示在调用消息时运行的方法是在运行时确定的,这有助于实现多态性。

另一方面,“Simula-style”通常似乎表明所有方法调用实际上只是编写重载函数调用的便捷方式 – 没有运行时多态性。 (如果我错了,请纠正我。)

在中间,我们有Java:默认情况下所有方法都是虚拟的,但是静态类型并且具有编译时类型调度。

例:

 // 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; }