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 .