Was ist der Unterschied zwischen abstrakten Datentypen und Objekten?

11

Eine Antwort auf Programmers.SE kennzeichnet einen Aufsatz von Cook ( Objekte sind keine ADTs ) als Spruch

  • Objekte verhalten sich eher wie eine charakteristische Funktion über die Werte eines Typs als wie eine Algebra. Objekte verwenden eher prozedurale Abstraktion als Typabstraktion

  • ADTs haben normalerweise eine eindeutige Implementierung in einem Programm. Wenn die eigene Sprache Module enthält, können mehrere Implementierungen eines ADT vorhanden sein, diese können jedoch normalerweise nicht zusammenarbeiten.

Es scheint mir, dass in Cooks Aufsatz zufällig ein Objekt als charakteristische Funktion für das spezifische Beispiel eines in Cooks Papier verwendeten Satzes angesehen werden kann . Ich denke nicht, dass Objekte im Allgemeinen als charakteristische Funktionen angesehen werden können.

Auch Aldritchs Artikel Die Macht der Interoperabilität: Warum Objekte unvermeidlich sind ¹ legt nahe

Die Definition von Cook identifiziert den dynamischen Versand im Wesentlichen als das wichtigste Merkmal des Objekts

mit Zustimmung dieser und mit Alan Kay , als er sagte

OOP bedeutet für mich nur Messaging, lokale Aufbewahrung und Schutz sowie das Verstecken von Staatsprozessen und die extreme Spätbindung aller Dinge.

Diese begleitenden Vorlesungsfolien zu Aldritchs Artikel legen jedoch nahe, dass Java Klassen ADTs sind, während Java-Schnittstellen Objekte sind - und tatsächlich können Schnittstellen "Objekte" zusammenarbeiten (eines der Hauptmerkmale von OOP, wie in einem der obigen Aufzählungspunkte angegeben) ).

Meine Fragen sind

  1. Kann ich zu Recht sagen, dass charakteristische Funktionen kein Schlüsselmerkmal von Objekten sind und dass Frank Shearar sich irrt?

  2. Sind Daten, die über Java-Schnittstellen miteinander kommunizieren, Beispiele für Objekte, obwohl sie keinen dynamischen Versand verwenden? Warum? (Mein Verständnis ist, dass der dynamische Versand flexibler ist und dass Schnittstellen ein Schritt in Richtung Messaging im Stil von Objective-C / Smalltalk / Erlang sind.)

  3. Bezieht sich die Idee des Abhängigkeitsinversionsprinzips auf die Unterscheidung zwischen ADTs und Objekten? (Siehe die Wikipedia-Seite oder The Talking Objects: Eine Geschichte über nachrichtenorientierte Programmierung ) Obwohl ich neu im Konzept bin, verstehe ich, dass es das Hinzufügen von Schnittstellen zwischen "Ebenen" eines Programms beinhaltet (siehe Wikipedia-Seitendiagramm).

  4. Bitte geben Sie weitere Beispiele / Erläuterungen zur Unterscheidung zwischen Objekten und ADTs an, wenn Sie dies wünschen.

¹ Dieser Artikel (veröffentlicht 2013) ist leicht zu lesen und fasst Cooks Artikel von 2009 mit Beispielen in Java zusammen. Ich empfehle dringend, es zumindest zu überfliegen, um diese Frage nicht zu beantworten, sondern nur, weil es ein gutes Papier ist.

LMZ
quelle
5
Interessant, aber bitte versuchen Sie, sich auf eine Frage pro Beitrag zu beschränken.
Raphael
es scheint eine Art (subtile?) akademische Unterscheidung / Debatte zu sein. Anscheinend sind ADTs fast Objekte, z. B. in Java und anderen modernen OOP-Sprachen. In vielen OOP-Sprachen wird Abstraktion als die Art und Weise angesehen, wie Objekte (in einer eingeschränkten / fokussierten Weise) die reale Welt modellieren. siehe auch verwirrt über die Definition von "Abstraktion" in OOP , Software Engineering
vzn

Antworten:

8

Google hat eine ähnliche Frage mit einer Antwort gestellt , die ich für sehr gut halte. Ich habe es unten zitiert.

Hier lauert eine weitere Unterscheidung, die in dem von mir verlinkten Cook-Aufsatz erklärt wird.

Objekte sind nicht die einzige Möglichkeit, die Abstraktion zu implementieren. Nicht alles ist ein Objekt. Objekte implementieren etwas, was manche Leute als prozedurale Datenabstraktion bezeichnen. Abstrakte Datentypen implementieren eine andere Form der Abstraktion.

Ein wesentlicher Unterschied tritt auf, wenn Sie binäre Methoden / Funktionen betrachten. Mit prozeduraler Datenabstraktion (Objekte) können Sie Folgendes für eine Int-Set-Schnittstelle schreiben:

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

Betrachten Sie nun zwei Implementierungen von IntSet, eine mit Listen und eine mit einer effizienteren binären Baumstruktur:

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

Beachten Sie, dass unionWith ein IntSet-Argument verwenden muss. Nicht der spezifischere Typ wie ListIntSet oder BSTIntSet. Dies bedeutet, dass die BSTIntSet-Implementierung nicht davon ausgehen kann, dass ihre Eingabe ein BSTIntSet ist, und diese Tatsache verwendet, um eine effiziente Implementierung zu erzielen. (Es könnte einige Laufzeit-Typ-Informationen verwenden, um sie zu überprüfen und einen effizienteren Algorithmus zu verwenden, wenn dies der Fall ist, aber es könnte immer noch ein ListIntSet übergeben werden und auf einen weniger effizienten Algorithmus zurückgreifen müssen).

Vergleichen Sie dies mit ADTs, bei denen Sie möglicherweise etwas Ähnliches in eine Signatur- oder Header-Datei schreiben:

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

Wir programmieren gegen diese Schnittstelle. Insbesondere bleibt der Typ abstrakt. Sie erfahren nicht, was es ist. Dann haben wir eine BST-Implementierung, die dann einen konkreten Typ und Operationen liefert:

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

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

Jetzt kennt Union tatsächlich die konkreten Darstellungen von s1 und s2, sodass sie diese für eine effiziente Implementierung nutzen kann. Wir können auch eine listengestützte Implementierung schreiben und stattdessen eine Verknüpfung damit herstellen.

Ich habe C (ish) -Syntax geschrieben, aber Sie sollten sich zB Standard ML für abstrakte Datentypen ansehen, die ordnungsgemäß ausgeführt wurden (wobei Sie z. B. tatsächlich mehr als eine Implementierung eines ADT im selben Programm verwenden können, indem Sie die Typen ungefähr qualifizieren: BSTImpl. IntSetStruct und ListImpl.IntSetStruct, sagen wir)

Das Gegenteil davon ist, dass Sie mit der prozeduralen Datenabstraktion (Objekte) problemlos neue Implementierungen einführen können, die mit Ihren alten funktionieren. Sie können beispielsweise Ihre eigene benutzerdefinierte LoggingIntSet-Implementierung schreiben und mit einem BSTIntSet verbinden. Dies ist jedoch ein Kompromiss: Sie verlieren informative Typen für binäre Methoden! Oft müssen Sie mehr Funktionen und Implementierungsdetails in Ihrer Benutzeroberfläche bereitstellen als bei einer ADT-Implementierung. Jetzt habe ich das Gefühl, ich schreibe nur noch den Cook-Aufsatz, also lies ihn wirklich!

Ich möchte ein Beispiel hinzufügen.

Cook schlägt vor, dass ein Beispiel für einen abstrakten Datentyp ein Modul in C ist. In der Tat beinhalten Module in C das Ausblenden von Informationen, da es öffentliche Funktionen gibt, die über eine Header-Datei exportiert werden, und statische (private) Funktionen, die dies nicht tun. Außerdem gibt es häufig Konstruktoren (z. B. list_new ()) und Beobachter (z. B. list_getListHead ()).

Ein wesentlicher Punkt, der beispielsweise ein Listenmodul mit dem Namen LIST_MODULE_SINGLY_LINKED zu einem ADT macht, ist, dass die Funktionen des Moduls (z. B. list_getListHead ()) davon ausgehen, dass die eingegebenen Daten vom Konstruktor von LIST_MODULE_SINGLY_LINKED im Gegensatz zu einem "Äquivalent" erstellt wurden "Implementierung einer Liste (zB LIST_MODULE_DYNAMIC_ARRAY). Dies bedeutet, dass die Funktionen von LIST_MODULE_SINGLY_LINKED bei ihrer Implementierung eine bestimmte Darstellung annehmen können (z. B. eine einfach verknüpfte Liste).

LIST_MODULE_SINGLY_LINKED kann nicht mit LIST_MODULE_DYNAMIC_ARRAY zusammenarbeiten, da wir keine Daten, die beispielsweise mit dem Konstruktor von LIST_MODULE_DYNAMIC_ARRAY erstellt wurden, an den Beobachter von LIST_MODULE_SINGLY_LINKED weitergeben können, da a_

Dies ist analog zu einer Methode, mit der zwei verschiedene Gruppen aus der abstrakten Algebra nicht zusammenarbeiten können (dh Sie können das Produkt eines Elements einer Gruppe nicht mit einem Element einer anderen Gruppe kombinieren). Dies liegt daran, dass Gruppen die Closure-Eigenschaft der Gruppe annehmen (das Produkt der Elemente in einer Gruppe muss in der Gruppe sein). Wenn wir jedoch beweisen können, dass zwei verschiedene Gruppen tatsächlich Untergruppen einer anderen Gruppe G sind, können wir das Produkt von G verwenden, um zwei Elemente hinzuzufügen, eines aus jeder der beiden Gruppen.

Vergleichen der ADTs und Objekte

  • Cook verbindet den Unterschied zwischen ADTs und Objekten teilweise mit dem Ausdrucksproblem. Grob gesagt sind ADTs mit generischen Funktionen gekoppelt, die häufig in funktionalen Programmiersprachen implementiert sind, während Objekte mit Java- "Objekten" gekoppelt sind, auf die über Schnittstellen zugegriffen wird. Für die Zwecke dieses Textes ist eine generische Funktion eine Funktion, die einige Argumente ARGS und einen Typ TYPE (Vorbedingung) enthält. basierend auf TYPE wählt es die entsprechende Funktion aus und wertet sie mit ARGS (Post-Condition) aus. Sowohl generische Funktionen als auch Objekte implementieren Polymorphismus, aber bei generischen Funktionen weiß der Programmierer, welche Funktion von der generischen Funktion ausgeführt wird, ohne den Code der generischen Funktion zu betrachten. Bei Objekten hingegen weiß der Programmierer nicht, wie das Objekt mit den Argumenten umgehen soll, es sei denn, der Programmierer betrachtet den Code des Objekts.

  • Normalerweise wird das Ausdrucksproblem als "Habe ich viele Darstellungen?" "Habe ich viele Funktionen mit wenigen Darstellungen". Im ersten Fall sollte man Code nach Repräsentation organisieren (wie es am häufigsten vorkommt, insbesondere in Java). Im zweiten Fall sollte man Code nach Funktionen organisieren (dh eine einzelne generische Funktion kann mehrere Darstellungen verarbeiten).

  • Wenn Sie Ihren Code nach Repräsentation organisieren, sind Sie es, wenn Sie zusätzliche Funktionen hinzufügen möchten gezwungen , um die Funktionalität zu jeder Darstellung des Objekts hinzuzufügen; In diesem Sinne ist das Hinzufügen von Funktionen nicht "additiv". Wenn Sie Ihren Code durch Funktionalität organisieren, dann, wenn Sie eine zusätzliche Darstellung hinzufügen möchten - Sie sind gezwungen , die Darstellung zu jedem Objekt hinzuzufügen; in diesem Sinne Hinzufügen von Darstellungen in nicht "additiv".

Vorteil von ADTs gegenüber Objekten

  • Das Hinzufügen von Funktionen ist additiv

  • Es ist möglich, das Wissen über die Darstellung eines ADT für die Leistung zu nutzen oder zu beweisen, dass das ADT unter bestimmten Voraussetzungen eine Nachbedingung garantiert. Dies bedeutet, dass es beim Programmieren mit ADTs darum geht, die richtigen Dinge in der richtigen Reihenfolge zu tun (Vor- und Nachbedingungen zu einer "Ziel" -Nachbedingung zu verketten).

Vorteile von Objekten gegenüber ADTs

  • Hinzufügen von Darstellungen im Additiv

  • Objekte können zusammenarbeiten

  • Es ist möglich, Vor- / Nachbedingungen für ein Objekt anzugeben und diese wie bei ADTs miteinander zu verketten. In diesem Fall haben Objekte den Vorteil, dass (1) Darstellungen leicht geändert werden können, ohne die Schnittstelle zu ändern, und (2) Objekte zusammenarbeiten können. Dies macht jedoch den Zweck von OOP im Sinne von Smalltalk zunichte. (siehe Abschnitt "Alan Kays Version von OOP)

Dynamischer Versand ist der Schlüssel zu OOP

Es sollte jetzt offensichtlich sein, dass ein dynamischer Versand (dh eine späte Bindung) für die objektorientierte Programmierung wesentlich ist. Auf diese Weise können Prozeduren generisch definiert werden, ohne dass eine bestimmte Darstellung erforderlich ist. Konkret sein - objektorientierte Programmierung ist in Python einfach, da Methoden eines Objekts so programmiert werden können, dass keine bestimmte Darstellung angenommen wird. Aus diesem Grund benötigt Python keine Schnittstellen wie Java.

In Java sind Klassen ADTs. Eine Klasse, auf die über die von ihr implementierte Schnittstelle zugegriffen wird, ist jedoch ein Objekt.

Nachtrag: Alan Kays Version von OOP

Alan Kay bezeichnete Objekte ausdrücklich als "Familien von Algebren", und Cook schlägt vor, dass eine ADT eine Algebra ist. Daher meinte Kay wahrscheinlich, dass ein Objekt eine Familie von ADTs ist. Das heißt, ein Objekt ist die Sammlung aller Klassen, die eine Java-Schnittstelle erfüllen.

Das Bild von Objekten, die von Cook gemalt wurden, ist jedoch weitaus restriktiver als Alan Kays Vision. Er wollte, dass sich Objekte als Computer in einem Netzwerk oder als biologische Zellen verhalten. Die Idee war, das Prinzip der geringsten Verpflichtung zur Programmierung anzuwenden, damit es einfach ist, Ebenen eines ADT auf niedriger Ebene zu ändern, sobald die Ebenen auf hoher Ebene mit ihnen erstellt wurden. Vor diesem Hintergrund sind Java-Schnittstellen zu restriktiv, da ein Objekt die Bedeutung einer Nachricht nicht interpretieren kann oder sie sogar vollständig zu ignorieren.

Zusammenfassend ist die Schlüsselidee von Objekten für Kay nicht, dass sie eine Familie von Algebren sind (wie Cook betont). Die Schlüsselidee von Kay bestand vielmehr darin, ein Modell, das im Großen (Computer in einem Netzwerk) funktioniert, auf das Kleine (Objekte in einem Programm) anzuwenden.

edit: Eine weitere Klarstellung zu Kays Version von OOP: Der Zweck von Objekten besteht darin, sich einem deklarativen Ideal zu nähern. Wir sollten dem Objekt sagen, was zu tun ist - nicht sagen, wie durch Mikromanagement der Zustand ist, wie es bei prozeduraler Programmierung und ADTs üblich ist. Weitere Informationen finden Sie hier , hier , hier und hier .

edit: Ich habe hier eine sehr, sehr gute Darstellung von Alan Kays Definition von OOP gefunden .

LMZ
quelle
3

Wenn Sie sich die ADT-Befürworter ansehen, betrachten sie eine ADT als das, was OOP eine Klasse nennen würde (interner, privater Status; eine begrenzte Anzahl von Operationen zulässig), aber es wird keine Beziehung zwischen Klassen (im Grunde keine Vererbung) berücksichtigt. Der Punkt ist stattdessen, dass das gleiche Verhalten mit verschiedenen Implementierungen erhalten werden kann. Beispielsweise kann eine Menge als Liste, Elemente in einem Array oder einer Hashtabelle oder als eine Art Baum implementiert werden.

vonbrand
quelle
2

Ich habe es immer so verstanden:

  1. Ein ADT ist eine Schnittstelle: Es ist nur eine Sammlung von Methoden, deren Typensignaturen, möglicherweise mit Vor- und Nachbedingungen.

  2. Eine Klasse kann ein oder mehrere ADTs implementieren , indem sie tatsächliche Implementierungen für die im ADT angegebenen Methoden angibt.

  3. Ein Objekt ist eine Instanz einer Klasse mit einer eigenen Kopie aller nicht statischen Variablen.

Es ist möglich, dass in der Literatur die Unterscheidungen unterschiedlich sind, aber dies ist die "Standard" -Terminologie, die Sie in der Informatik hören werden.

In Java Collectionist beispielsweise ein ADT ArrayListeine Klasse, und Sie können ArrayListmit dem newOperator ein Objekt erstellen.

Die Aussage, dass ADTs normalerweise nur eine Implementierung haben, ist häufig nicht der Fall. Es ist beispielsweise möglich, dass Sie in Ihrem Programm sowohl baumbasierte als auch hashtabellenbasierte Wörterbücher verwenden möchten, je nachdem, was Sie speichern. Sie würden ein ADT gemeinsam nutzen, aber unterschiedliche Implementierungen verwenden.

jmite
quelle
1
Haben ADTs aus Sicht der funktionalen Programmierung nicht bestimmte Einschränkungen, die Klassen im Allgemeinen nicht haben?
Raphael
@ Raphael Wie was?
jmite
1
Dies ist eine verbreitete Ansicht von ADTs und eine vernünftige Annäherung. Nach meinem Verständnis haben ADTs, wie sie in der PL-Literatur berücksichtigt und formal definiert sind, tatsächlich eine etwas spezifischere Bedeutung. Ein ADT ist eine Spezifikation einer Art Datenstruktur: nicht wie es implementiert ist oder wie die Daten dargestellt werden, sondern die Schnittstelle dazu (welche Arten von Operationen können ausgeführt werden?) Und das Verhalten / die Semantik jeder dieser Operationen. Es handelt sich also nicht nur um eine Java-Schnittstelle (eine Liste von Methoden mit Typensignaturen), sondern auch um eine Spezifikation ihres Verhaltens.
DW
1
Mein Eindruck ist zum Beispiel, dass die Java- CollectionSchnittstelle kein ADT ist. Es enthält eine Liste von Methoden, gibt jedoch nicht deren Semantik an. Bietet es die Semantik einer Menge? ein Multiset (Tasche)? eine geordnete Liste? Das bleibt unbestimmt. Ich bin mir also nicht sicher, ob es als ADT gilt. Das ist mein Eindruck, aber es ist durchaus möglich, dass mein Verständnis falsch ist ...
DW
In den Vorlesungsfolien, auf die ich verlinkt habe, wird eine Java- Klasse (nicht einmal eine Schnittstelle!) Als ADT betrachtet, da eine Klasse sowohl private als auch öffentliche Teile hat (ich gehe davon aus, dass ein Teil der Klasse informell angegeben wird, bin mir aber nicht sicher). . Andererseits wird eine Klasse, auf die über eine Schnittstelle zugegriffen wird, als Objekt betrachtet, wobei die von der Schnittstelle definierten Methoden "Nachrichten" sind (Absichten auf hoher Ebene). Wenn Objekte durch Absichten miteinander sprechen, können verschiedene Implementierungen eines Objekts miteinander "sprechen".
LMZ