抽象数据类型和对象之间有什么区别?


11

关于Programmers.SE答案描述了Cook的一篇论文(对象不是ADT)说的是

  • 对象在类型的值上表现得像特征函数,而不是代数。对象使用过程抽象而不是类型抽象

  • ADT通常在程序中具有唯一的实现。当某人的语言具有模块时,可以实现ADT的多种实现,但通常不能互操作。

在我看来,在库克的论文中,恰好是这样一种情况:对于库克论文中使用的集合的特定示例,可以将一个对象视为特征函数。我不认为对象通常可以视为特征函数。

另外,Aldritch的论文互操作性的力量:为什么对象不可避免 ¹暗示了

Cook的定义实质上将动态调度识别为对象的最重要特征

同意这一点,并同意艾伦·凯说的话

对我来说,OOP意味着仅消息传递,本地保留和保护以及状态过程的隐藏以及所有事物的极端后期绑定。

但是,这些伴随 Aldritch论文的演讲幻灯片表明,Java 是ADT,而Java接口是对象-的确,使用接口“对象”可以互操作(如上面的要点之一所述,OOP的主要功能之一) )。

我的问题是

  1. 我是否正确地说特征函数不是对象的关键特征,而弗兰克·希亚尔(Frank Shearar)错误呢?

  2. 即使对象不使用动态分派,数据是否仍通过Java接口的对象示例相互通信?为什么?(我的理解是动态调度更加灵活,并且接口是迈向目标C / smalltalk / erlang样式消息传递的一步。)

  3. 依赖倒置原理的思想是否与ADT和对象之间的区别有关?(请参阅Wikipedia页面The Talking Objects:有关面向消息的编程的故事)尽管我是这个概念的新手,但我了解它涉及在程序的“各层”之间添加接口(请参阅Wikipedia页面图)。

  4. 如果需要,请提供其他示例/说明,以区分对象和ADT。

¹ 本文(于2013年出版)易于阅读,并以Java实例总结了Cook的2009年论文。我强烈建议至少略读一下,不要回答这个问题,而只是因为它是一篇好论文。


5
有趣,但请尝试将自己限制为每个帖子一个问题。
拉斐尔

这似乎有点(微妙?)学术上的区别/争论。显然,ADT几乎是对象,例如在Java和其他现代OOP语言中。在许多OOP语言中,抽象被视为对象(以受约束/集中的方式)对现实世界建模的方式。另请参见OOP中关于“抽象”的定义感到困惑软件工程
vzn 2016年

Answers:


8

Google提出了一个类似的问题,并给出了我认为很好的答案。我在下面引用了它。

在我链接的库克文章中,还解释了另一个潜伏的区别。

对象不是实现抽象的唯一方法。并非所有事物都是对象。对象实现某些人称为过程数据抽象的东西。抽象数据类型实现了另一种抽象形式。

当考虑二进制方法/函数时,会出现一个关键的区别。使用过程数据抽象(对象),您可以为Int set接口编写如下代码:

interface IntSet {
  void unionWith(IntSet s);
  ...
}

现在考虑IntSet的两种实现,例如一种由列表支持的方法,以及一种由更有效的二叉树结构支持的方法:

class ListIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
} 
class BSTIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
}

请注意,unionWith必须采用IntSet参数。不是更具体的类型,如ListIntSet或BSTIntSet。这意味着BSTIntSet实现不能假定其输入是BSTIntSet并使用该事实来给出有效的实现。(它可以使用一些运行时类型信息进行检查,如果可以的话,可以使用效率更高的算法,但是仍然可以将其传递给ListIntSet,并且必须使用效率较低的算法)。

将此与ADT进行比较,您可以在ADT中在签名或头文件中编写类似于以下内容的内容:

typedef struct IntSetStruct *IntSetType;
void union(IntSetType s1, IntSetType s2);

我们针对此接口进行编程。值得注意的是,该类型保留为抽象。您不知道它是什么。然后我们有一个BST实现,然后提供了一个具体的类型和操作:

struct IntSetStruct {
 int value;
 struct IntSetStruct* left;
 struct IntSetStruct* right;
}

void union(IntSetType s1, IntSetType s2){ ... }

现在,union实际上知道s1和s2的具体表示,因此可以利用它进行有效的实现。我们还可以编写一个列表支持的实现,然后选择与之链接。

我已经编写了C(ish)语法,但是您应该查看例如标准ML,以便正确完成抽象数据类型(例如,您可以在同一程序中实际使用多个ADT实现,大致可以通过限定类型:BSTImpl)。比如说IntSetStruct和ListImpl.IntSetStruct)

相反的是,过程数据抽象(对象)使您可以轻松地引入可以与旧的实现一起使用的新实现。例如,您可以编写自己的自定义LoggingIntSet实现,并将其与BSTIntSet合并。但这是一个折衷方案:您会丢失二进制方法的信息类型!与ADT实现相比,通常您不得不在界面中暴露更多的功能和实现细节。现在,我觉得我只是在重新整理库克论文,所以,请真正阅读它!

我想为此添加一个示例。

Cook建议使用C语言中的模块作为抽象数据类型的示例。实际上,C语言中的模块涉及信息隐藏,因为存在通过头文件导出的公共功能,而没有通过头文件导出的静态(私有)功能。另外,通常会有构造函数(例如list_new())和观察者(例如list_getListHead())。

例如,使名为LIST_MODULE_SINGLY_LINKED的列表模块成为ADT的关键点在于,该模块的功能(例如list_getListHead())假定输入的数据已由LIST_MODULE_SINGLY_LINKED的构造函数创建,而不是任何“等价的”清单的执行方式(例如LIST_MODULE_DYNAMIC_ARRAY)。这意味着LIST_MODULE_SINGLY_LINKED的功能可以在其实现中采用特定的表示形式(例如,单链接列表)。

LIST_MODULE_SINGLY_LINKED无法与LIST_MODULE_DYNAMIC_ARRAY进行互操作,因为我们无法将使用LIST_MODULE_DYNAMIC_ARRAY的构造函数创建的数据馈送给LIST_MODULE_SINGLY_LINKED的观察者,因为LIST_MODULE_SINGLY_LINKED假定对象仅表示一个列表(表示一个对象)。

这类似于抽象代数的两个不同组无法互操作的方式(也就是说,您不能将一个组的元素与另一组的元素的乘积取整)。这是因为组具有组的闭包属性(组中元素的乘积必须在组中)。但是,如果我们可以证明两个不同的组实际上是另一个组G的子组,那么我们可以使用G的乘积来添加两个元素,两个组中的每一个都可以。

比较ADT和对象

  • Cook将ADT和对象之间的差异部分与表达问题联系起来。粗略地说,ADT与通常以功能性编程语言实现的泛型函数耦合,而对象与通过接口访问的Java“对象”耦合。出于本文的目的,泛型函数是一个带有一些参数ARGS和类型TYPE(前提条件)的函数;基于TYPE,它选择适当的函数,并使用ARGS(后置条件)对其进行评估。泛型函数和对象都实现了多态性,但是对于泛型函数,程序员知道该函数将由泛型函数执行,而无需查看泛型函数的代码。另一方面,对于对象,除非程序员查看对象的代码,否则程序员不知道对象将如何处理参数。

  • 通常,表达问题是根据“我有很多表示形式”来考虑的吗?与“我有很多功能却很少代表”吗?在第一种情况下,应该通过表示来组织代码(这是最常见的,尤其是在Java中)。在第二种情况下,应该按功能组织代码(即,让一个通用功能处理多种表示形式)。

  • 如果您代表组织代码,然后,如果你想添加额外的功能,你是被迫的功能添加到对象的每个表示; 从这个意义上说,添加功能不是“添加”。如果你按功能组织代码,然后,如果你想添加一个额外的代表性-你是被迫的表示添加到每个对象; 从这个意义上说,不是以“加法”形式添加表示形式。

ADT相对于对象的优势

  • 添加功能是附加的

  • 可以利用ADT表示形式的知识来提高性能,或者证明ADT在给定前提条件下可以保证某些后置条件。这意味着使用ADT进行编程是按照正确的顺序进行正确的操作(将先决条件和后置条件链接在一起,形成“目标”后置条件)。

对象相对于ADT的优势

  • 在加法器中添加表示

  • 对象可以互操作

  • 可以指定对象的前置/后置条件,并将这些条件链接在一起,就像ADT一样。在这种情况下,对象的优点是:(1)无需更改接口即可轻松更改表示形式;(2)对象可以互操作。但是,这在小谈话的意义上违背了OOP的目的。(请参阅“艾伦·凯的OOP版本”部分)

动态调度是OOP的关键

现在显而易见,动态分配(即后期绑定)对于面向对象的编程至关重要。这样就可以以通用方式定义过程,而无需采用特定的表示形式。具体来说-面向对象的编程在python中很容易,因为可以以不采用特定表示方式的方式来编程对象的方法。这就是为什么python不需要Java之类的接口的原因。

在Java中,类是ADT。但是,通过其实现的接口访问的类是一个对象。

附录:Alan Kay的OOP版本

艾伦·凯(Alan Kay)明确将对象称为“代数族”,而库克则认为ADT是代数。因此,凯很可能意味着一个对象是ADT族。也就是说,对象是满足Java接口的所有类的集合。

但是,库克画的物体的图片比艾伦·凯的想象要严格得多。他希望物体表现为网络中的计算机或生物细胞。这个想法是将最小承诺原则应用到编程中-这样,一旦使用ADT构建了高层,就很容易更改它们的底层。考虑到此图,Java接口的限制性太强,因为它们不允许对象解释消息的含义,甚至完全不予理ignore。

总而言之,对于凯来说,对象的关键思想不是说它们是一个代数族(正如库克所强调的那样)。相反,Kay的关键思想是将适用于大型(网络中的计算机)的模型应用于小型(程序中的对象)的模型。

编辑:关于凯的OOP版本的另一个说明:对象的目的是向声明性理想靠拢。我们应该告诉对象该怎么做-而不是告诉它如何通过微观管理实现状态,这是过程编程和ADT的惯例。在此处此处此处此处可以找到更多信息。

编辑:我发现OOP的阿兰凯的定义的一个非常,非常好的博览会在这里


3

如果您看一下ADT的支持者,他们会认为ADT是OOP所称的类(内部,私有状态;允许的一组有限操作),但没有考虑类之间的关系(基本上没有继承)。关键是可以通过不同的实现方式获得相同的行为。例如,一个集合可以实现为列表,数组或哈希表中的元素或某种树。


2

我一直这样理解:

  1. ADT是一个接口:它只是方法的集合,它们的类型签名可能带有前后条件。

  2. 通过为ADT中指定的方法提供实际的实现,一类可以实现一个或多个ADT。

  3. 对象是类的实例,具有任何非静态变量的自己的副本。

文献中的区别可能会有所不同,但这是您在计算机科学中会听到的“标准”术语。

例如,在Java中,它Collection是一个ADT,ArrayList是一个类,您可以ArrayList使用new运算符创建一个对象。

至于ADT通常只有一个实现的说法,通常不是这样。例如,根据存储的内容,您可能希望在程序中同时使用基于树的字典和基于哈希表的字典。他们将共享一个ADT,但将使用不同的实现。


1
从功能编程的角度来看,ADT是否具有某些类通常没有的限制?
拉斐尔

@Raphael喜欢什么?
jmite 2016年

1
这是ADT的常见视图,并且是一个合理的近似值。但是,据我了解,PL文献中所考虑并正式定义的ADT实际上具有更具体的含义。ADT是一种数据结构的规范:不是它的实现方式或数据表示方式,而是它的接口(可以执行哪些类型的操作?)以及每个操作的行为/语义。因此,它不仅是Java接口(带有类型签名的方法列表),而且是其行为的规范。
DW

1
例如,我的印象是Java Collection接口不是ADT。它提供了方法列表,但未指定其语义。它是否提供集合的语义?多件套(袋子)?有序列表?尚未说明。因此,我不确定它是否可以算作ADT。那是我的印象,但我的理解很可能是错误的……
DW

在我链接的讲座幻灯片中,Java (甚至不是接口!)都被视为ADT,因为一个类具有私有部分和公共部分(我假设该类的一部分将被非正式地指定,但我不确定) 。另一方面,通过接口访问的类被视为对象,接口定义的方法为“消息”(高级意图)。当对象通过意图彼此交谈时,对象的不同实现可以彼此“交谈”。
LMZ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.