更广泛的 OOP 横幅中的几个关键“风格”差异。
在所有情况下,关于 static 或 dynamic 类型系统的陈述主要意味着其中一个或另一个,问题远未明确或明确定义。
许多语言也选择模糊选项之间的界限,因此这绝不是二元选择列表。
或“foo.Bar(x) 是什么意思?”
- 类型的层次结构被扁平化为每个实例的特定实现(通常通过vtable 完成),并且通常允许显式引用基类实现。
- 从概念上讲,您会查看 foo 在调用点的最具体类型。如果调用的参数 x 具有 Bar 的实现,则选择 foo 的父级并重复该过程。
- 示例:C++/Java/C#,经常使用“Simula style”。
- 纯消息传递。 foo 中处理“名为”“Bar”的消息的代码被要求接受 x。只有名称很重要,而不是呼叫站点可能对 Bar 的确切含义有任何假设。与以前的风格形成对比,在这种风格中,所讨论的方法是 Bar 已知是在编译时定义的类型层次结构上定义的东西(尽管层次结构中的精确位置留到运行时)。
1 通常在静态类型的框架中使用,它是一个错误,在编译时检查是否存在这样的实现。此外,如果 x 和 y 是不同的类型,语言通常会区分 Bar(x) 和 Bar(y)。这是方法重载,产生的同名方法被视为完全不同。
2 常用于动态语言(倾向于避免方法重载),因为在运行时,foo 的类型可能对名为“Bar”的消息没有“处理程序”,不同的语言以不同的方式处理它方式。
如果需要,两者都可以在幕后以相同的方式实现(通常第二个,Smalltalk 风格的默认设置是调用一个函数,但这并不是在所有情况下都定义为行为)。
由于前一种方法通常可以很容易地实现为简单的指针偏移函数调用,因此可以更容易地使其相对快速。这并不意味着其他样式也不能快速制作,但可能需要做更多的工作来确保这样做时不会损害更大的灵活性。
继承/重用
或“婴儿从哪里来?”
-
Class based
- 方法实现被组织成称为类的组。当需要实现继承时,定义一个类扩展父类。通过这种方式,它获得了父级(字段和方法)所有公开的方面,并且可以选择更改某些/所有这些方面,但不能删除任何方面。您可以添加和更新,但不能删除。
- 示例:C++/Java/C#(注意 SmallTalk 和 Simula 都使用这个)
-
Prototype based
- 对象的任何实例都只是已识别方法(通常由名称识别)和以(再次命名)字段形式的状态的集合。每当需要这种“类型”的新实例时,都可以使用现有实例来克隆一个新实例。这个新类保留了前一个类的状态和方法的副本,但随后可以进行修改以删除、添加或更改现有的命名字段和方法。
- 示例:Self/JavaScript
同样 1 倾向于发生在静态语言中,2 倾向于发生在动态语言中,尽管这绝不是他们简单地适应这种风格的要求。
基于接口或类
或“什么或如何?”
- 接口列出了所需的方法。他们是契约
- 类列出了必需但可以选择提供其实现的方法
这不是一个二元选择。大多数基于类的语言都允许抽象方法的概念(还没有实现的)。如果您有一个所有方法都是抽象的类(在 C++ 中称为纯虚拟),那么该类几乎就是一个接口,尽管它可能还定义了一些状态(字段)。一个真正的接口应该没有状态(因为它只定义什么是可能的,而不是它如何发生。
只有较旧的 OOP 语言倾向于仅依赖其中一种。
VB6只有on接口,没有实现继承。
Simula 允许您声明纯虚拟类,但您可以实例化它们(使用时出现运行时错误)
单继承或多继承
或“谁是爸爸?”
- 单
- 只有一种类型可以是另一种类型的父级。在上面的基于类的表单中,您只能扩展(实现)一种类型。通常这种形式包括接口的概念作为语言的第一类方面来弥补这一点。
- 优势包括更清晰的元数据和内省,更简单的语言规则。
- 复杂性包括使将有用的方法纳入范围变得更加困难(MixIns 和 Extension methods 之类的东西试图缓解此类问题)
- 示例:C#/java
- Multiple - 您可以扩展多个类
- 优点包括某些结构更容易建模和设计
- 复杂性包括解决冲突的复杂规则,尤其是当存在可能采用任一父类型的重载方法时。
- 示例:C++/Eiffel
这个问题引发了相当大的争论,尤其是因为它是 C++ 的 OOP 实现与许多现代静态类型语言(如 c# 和 java 等可能的继任者)之间的关键区别。
可变性
或者“你想对我做什么?”
- 可变的
- 不可变
通常这不是全有或全无,它只是一个默认值(最常用的 OOP 语言默认为可变)。这会对语言的结构产生很大影响。许多包含 OOP 特性的主要函数式语言默认对象具有不可变状态。
他们的 OOP 的“纯粹性”
或“一切都是对象吗?”
- 绝对系统中的一切都被视为一个对象(甚至可能下至方法本身,它们只是另一种对象,可以像其他对象一样进行交互)。
- 并非所有事物都是对象,您不能将消息传递给所有事物(尽管系统可能会跳出圈子来让它看起来像是可以传递的)
这是相当复杂的,因为像原语的自动装箱这样的技术让它看起来一切都是如此,但你会发现存在几种边界情况,其中发现了这种“编译器魔法”,并且在幕后发现了众所周知的 Oz 巫师,结果是问题或错误。
在默认具有不变性的语言中,这种情况不太可能发生,因为对象的关键方面(它们包含方法 和状态)意味着与对象相似但不太可能发生的事情并发症。
- 关于 Java/C#,autoboxing(或c#)系统让您在语法上将任何变量视为对象,但实际上并非如此这表现在尝试锁定自动装箱对象(被编译器拒绝,因为这将是一个明显的错误)等领域。
静态或动态
或“你认为你是谁?”
语言设计的一个更为普遍的方面,这里不赘述,但这一决定中固有的选择会影响前面提到的 OOP 的许多方面。
只是多态后期绑定的一些方面可以依赖于:
- 要向其传递消息的对象的类型(在编译时/运行时)
- 正在传递的参数类型(s)(在编译时/运行时)
语言越动态,这些决策往往变得越复杂,但相反,语言用户的输入越多,而不是设计者在决策中使用的语言。
在这里举个例子会有些鲁莽,因为静态类型的语言可能会被修改为包含动态方面(如 c# 4.0)。