您要问的基本上是面向对象的数据抽象和具有抽象数据类型的数据抽象之间的区别(ADTs,不要与代数数据类型混淆,代数数据类型通常也缩写为“ADT”)。 (见On Understanding Data Abstraction, RevisitedWilliam R. Cook、他的Proposal for Simplified, Modern Definitions of "Object" and "Object Oriented"和Object-Oriented Programming Versus Abstract Data Types。)
相同抽象数据类型的两个不同实例可以检查彼此的表示(但不能检查其他抽象数据类型实例的表示),而一个对象可以从不检查另一个对象的表示,即使该对象是 same 类型的实例。 Cook 将此属性识别为面向对象的数据抽象(实际上是一般的面向对象)的基本和定义特征,并将其称为 Autognosis(自我知识)。我更喜欢术语Xenoagnosis(外国不知识),它是由 Glenn Vanderburg、Rick de Natale 或已故的 IIRC 的 Jim Weirich 应用的,因为它强调了两种方法之间的区别,即对象无法知道其他对象。
如您所见,在 Scala 中,可以通过 private[this] 访问修饰符实现面向对象的数据抽象。
class TestPrivate {
private val notReallyPrivate = 42
private[this] val privateIMeanIt = 23
def blowUp(other: TestPrivate) = {
other.notReallyPrivate
other.privateIMeanIt
}
}
// error: value privateIMeanIt is not a member of TestPrivate
在 Erlang 中,Object 的等价物是 Process,而 Process 被语言语义完全封装(它们甚至有自己的垃圾回收内存)。毕竟,他们甚至可以生活在不同大陆的不同机器上,因此公开表示是不切实际的。您向进程发送消息,进程完全自主地决定如何处理消息以及如何响应消息。 一切基本上都是private[this]。
在 Clojure 中,可以通过闭包实现面向对象的数据抽象。 (这对于几乎任何具有闭包和高阶子例程的语言都是如此,例如,您也可以在 Erlang 中将函数用作对象而不是进程。)想想 ECMAScript:尽管它有一个令人困惑的语言结构,称为“对象”,在ECMAScript 对象实际上是用函数实现的。您可以以同样的方式在 Clojure 中实现它们。数据抽象的面向对象形式也称为过程数据抽象,因为它不依赖于类型,而是依赖于将数据隐藏在过程接口(或功能接口,如果你想要的话,毕竟可变性和副作用与 OO 正交)。库克(半开玩笑地)争辩说,因为 λ-演算只有函数,所以所有抽象都是函数式的,因此 λ-演算是第一个、最古老和最纯粹的 OO 语言——显然这意味着 OO 数据抽象必须在密切相关的语言中成为可能基于 λ 演算,例如 Lisp 家族。 (历史轶事:Scheme最初是为了研究OO和Actors而设计的,他们在实现过程中才注意到消息发送和函数调用的解释器的代码路径是相同的。)
在 Java 或 C♯ 等语言中,面向对象是使用类型系统和程序员纪律实现的,仅使用 interfaces 作为类型。 interfaces 无法描述表示,因此,只要您确保(通过程序员纪律、编码风格、代码审查,也许是静态分析工具)只有 interfaces 被用作类型(每个局部变量、字段、方法参数、方法返回值、强制转换运算符、instanceof 检查和通用类型参数必须始终为interface) 和classes仅用作工厂(唯一允许出现类名的地方是直接在new关键字旁边),那么你就实现了面向对象的数据抽象。 (还有一些其他注意事项,最重要的是,您不能使用引用相等。)
interface ITestPrivate {
default int thisIsPublic() { return 0; }
default void blowUp1(ITestPrivate other) {
other.thisIsPublic();
other.privateIMeanIt();
}
}
class TestPrivate implements ITestPrivate {
private int privateIMeanIt() { return 23; }
void blowUp2(TestPrivate other1, ITestPrivate other2) {
other1.notReallyPrivate(); // works
other2.notReallyPrivate(); // doesn't
}
}
这样做应该在 F♯ 中也是可能的,因为 F♯ supports interfaces as well。