Gibt es ein anderes Verwendungsprinzip für abstrakte Klassen / Interfaces in C ++ und Java?

13

Laut Herb Sutter sollte man abstrakte Interfaces (alle rein virtuellen Funktionen) abstrakten Klassen in C ++ vorziehen, um die Implementierung so weit wie möglich zu entkoppeln. Obwohl ich diese Regel persönlich sehr nützlich finde, bin ich kürzlich einem Team mit vielen Java-Programmierern beigetreten, und im Java-Code scheint diese Richtlinie nicht zu existieren. Funktionen und ihre Implementierungen befinden sich sehr häufig in abstrakten Klassen. Habe ich Herb Sutter auch für C ++ falsch verstanden oder gibt es einen allgemeinen Unterschied in der Verwendung von abstrakten Funktionen in C ++ im Vergleich zu Java. Sind abstrakte Klassen mit Implementierungscode in Java sinnvoller als in C ++ und wenn ja, warum?

Martin
quelle
1
Ich hatte einige Zweifel und habe es schließlich hierher gebracht, weil es an einigen Gestaltungsprinzipien liegen könnte, die mir am Java oo fehlen. Dann geht es also nicht um einen allgemeinen Rat, sondern mehr um das Richtige und das Falsche, um die Sprache zu benutzen
Martin
Schnittstellen sollen rein virtuell sein. Die Idee von abstrakten Klassen ist, dass sie teilweise implementiert sind und es an der Implementierung liegt, die Lücken zu füllen, ohne den Code unnötig zu wiederholen (z. B. warum in jeder Unterklasse schreiben (byte) und schreiben (int), wenn Sie das haben können abstract class call schreibe (byte) von schreibe (int))
1
Möglicherweise verwandt: stackoverflow.com/q/1231985/484230 gibt einen Grund an, warum abstrakte Klassen in Java bevorzugt werden. Für C ++ scheint dieser Grund nicht zuzutreffen, da es freie Funktionen gibt, die Funktionen auf der Schnittstellenebene hinzufügen können
Martin
1
Ich denke, die goldene Regel ist, "Nicht-Blatt-Klassen abstrakt zu machen", aber das macht keine "nur reinen" oder "leeren" Anforderungen.
Kerrek SB
1
Wenn es für Sie funktioniert, funktioniert es für Sie. Ich verstehe wirklich nicht, warum die Leute in Panik geraten, wenn ihr Code nicht mehr den neuesten Meinungen entspricht.
James

Antworten:

5

OOP hat Zusammensetzung und Substitution.

C ++ verfügt über Mehrfachvererbung, Template-Spezialisierung, Einbettung und Wert- / Verschiebungs- / Zeigersemantik.

Java verfügt über Einzelvererbung und Schnittstellen, Einbettungs- und Referenzsemantik.

Die OOP-Schule verwendet diese Sprachen üblicherweise, indem sie die Vererbung für die Objektsubstitution und die Einbettung für die Komposition einsetzt. Sie brauchen aber auch einen gemeinsamen Vorfahren und einen Weg zur Laufzeitumwandlung (in C ++ heißt das dynamic_cast, in Java wird nur eine Schnittstelle von einer anderen abgefragt).

Java tut dies alles durch seine eigene java.lang.Objectverwurzelte Hierarchie. C ++ hat kein vordefiniertes gemeinsames Stammverzeichnis, daher sollten Sie es zumindest definieren, um zu einem "Bild" zu gelangen (dies schränkt jedoch einige C ++ - Möglichkeiten ein ...).

Danach kann die Möglichkeit, Polymorphismus zur Kompilierungszeit (nach CRTP denken) und Wertsemantik zu haben, auch andere Alternativen zu der Art und Weise bieten, wie das Konzept des "OOP-Objekts" in ein C ++ - Programm portiert werden kann.

Sie können sich sogar die Häresie vorstellen, die Einbettung und implizite Konvertierung zur Verwaltung der Substitution und die private Vererbung zur Verwaltung der Komposition zu verwenden, wobei das traditionelle Schulparadigma umgekehrt wird. (Natürlich ist dieser Weg 20 Jahre jünger als der andere. Erwarten Sie also keine breite Unterstützung durch die Community.)

Oder Sie können sich eine virtuelle gemeinsame Basis für alle Klassen vorstellen, von der Schnittstelle (keine Implementierung) bis zur endgültigen Klasse (vollständig implementiert), die teilweise implementierte Schnittstellen und gerade Schnittstellencluster durchläuft, wobei "Dominanz" als Versand von Schnittstelle zu Implementierungen über einen "Multi-Stack" verwendet wird -parallelogram "Vererbungsschema.

Der Vergleich von OOP mit Java mit C ++ unter der Annahme, dass es nur einen einzigen OOP-Weg gibt, schränkt die Fähigkeiten beider Sprachen ein.

Das Erzwingen der strikten Einhaltung von Java-Codierungsidiomen durch C ++ bewirkt eine Denaturierung von C ++, da Java gezwungen wird, sich wie eine C ++ - ähnliche Sprache zu verhalten, was Java denaturiert.

Geht es nicht um "Sensibilität", sondern um unterschiedliche "Aggregationsmechanismen", die die beiden Sprachen haben, und um sie auf unterschiedliche Weise zu kombinieren, was eine Redewendung in einer Sprache rentabler macht als in der anderen und umgekehrt.

Emilio Garavaglia
quelle
1
Ich halte diese Antwort für sehr interessant, da sie Sprachfunktionen nur als Hilfe und nicht als Lehre beschreibt. Sie benötigen jedoch kein gemeinsames Stammverzeichnis, wenn Sie in c ++ oo ausführen möchten. Dies ist einfach falsch, auch weil Sie Operatoren und Vorlagen haben (die, wie Sie auch betont haben, eine sehr leistungsfähige Alternative zum Hauptbaumdesign von Java darstellen). Abgesehen davon sind Ihre Punkte die wertvollsten in allen Antworten
Martin
1
@ Martin: In „technischen Sinne“ Du hast Recht, aber wenn Sie polymorophism Laufzeit muss (da die tatsächliche Art der instanzierte Objekte auf Programmeingabe abhängt) eine „root“ ( ‚a‘ ist ein Artikel, nicht eine Abkürzung für " one and only ") macht alle Objekte zu" Cousins ​​"und die Hierarchie laufzeitbegehbar. Unterschiedliche Wurzeln haben unterschiedliche Abstammungen, die nicht miteinander verwandt sind. Ob dies "gut" oder "schlecht" ist, ist eine Frage des Kontexts, nicht der Redewendung.
Emilio Garavaglia
Das ist richtig. Ich dachte, Sie beziehen sich auf die künstliche Erstellung einer allgemeinen Wurzel für ein gesamtes C ++ - Programm und sahen darin einen Mangel, dass es im Vergleich zu Java nicht vorhanden ist. Aber nach Ihrer Bearbeitung machen Sie den Punkt ganz klar.
Martin
12

Das Prinzip gilt für beide Sprachen, aber Sie führen keinen fairen Vergleich durch. Sie sollten reine abstrakte C ++ - Klassen mit Java-Schnittstellen vergleichen.

Selbst in C ++ können abstrakte Klassen vorhanden sein, in denen einige der Funktionen implementiert sind, die jedoch von einer reinen abstrakten Klasse abgeleitet sind (keine Implementierungen). In Java haben Sie dieselben abstrakten Klassen (mit einigen Implementierungen), die von Interfaces abgeleitet werden können (keine Implementierungen).

Luchian Grigore
quelle
Wann bevorzugen Sie eine abstrakte Klasse gegenüber einer Interface-Klasse in c ++? Ich habe mich immer für Interface und Nicht-Member-Funktionen in C ++ entschieden.
Martin
1
@Martin das kommt auf das design an. Bevorzugen Sie grundsätzlich immer eine Schnittstelle. Aber " immer " Regeln haben Ausnahmen ...
Luchian Grigore
Richtig, aber im Java-Code sehe ich abstrakte Klassen, die größtenteils die Mehrheit repräsentieren. Könnte dies an der Tatsache liegen, dass freie Funktionen, die an Schnittstellen arbeiten, in Java nicht möglich sind?
Martin
3
@Martin gut freie Funktionen sind in Java überhaupt nicht möglich, also könnte das ein Grund sein, ja. Gute Stelle! Beantwortete deine eigene Frage! Sie können selbst eine Antwort hinzufügen, ich denke, das ist es.
Luchian Grigore
4

Im Allgemeinen gelten für Java und C ++ dieselben OO-Prinzipien. Ein großer Unterschied ist jedoch, dass C ++ die Mehrfachvererbung unterstützt, während Sie in Java nur von einer Klasse erben können. Dies ist der Hauptgrund, warum Java meiner Meinung nach Schnittstellen hat, um das Fehlen von Mehrfachvererbung zu ergänzen und wahrscheinlich einzuschränken, was Sie damit machen können (da es viel Kritik am Missbrauch von Mehrfachvererbung gibt). Wahrscheinlich gibt es für Java-Programmierer eine stärkere Unterscheidung zwischen abstrakten Klassen und Schnittstellen. Abstrakte Klassen werden verwendet, um das Verhalten zu teilen und zu erben, während Schnittstellen lediglich dazu dienen, zusätzliche Funktionen hinzuzufügen. Denken Sie daran, in Java können Sie nur von einer Klasse erben, aber Sie können viele Schnittstellen haben. In C ++ jedoch reine abstrakte Klassen (dh eine „C ++ Schnittstelle“) sind Wird verwendet, um Verhalten zu teilen und zu erben, das nicht dem Zweck einer Java-Schnittstelle entspricht (obwohl Sie die Funktionen noch implementieren müssen). Daher unterscheidet sich die Verwendung von Java-Schnittstellen.

Jesse Gut
quelle
0

Manchmal ist eine Standardimplementierung sinnvoll. Beispiel: Eine generische PrintError-Methode (String-Nachricht), die für alle Unterklassen gilt.

virtual PrintError(string msg) { cout << msg; }

Es kann immer noch überschrieben werden, wenn es wirklich erforderlich ist, aber Sie können dem Client Ärger ersparen, indem Sie ihm erlauben, nur die generische Version aufzurufen.

Science-Fiction
quelle