Braucht man Mehrfachvererbung
-
audacia schrieb:
Derived* d = 0; Base* b = static_cast <Base*> (d);
Garantiert der Standard, daß b hier 0 ist, auch wenn Base die zweite Basisklasse von Derived ist?
ja. Das geht ja auch implizit (und static_cast führt die Konvertierung immer so aus, wie es implizit geschehen würde, wenn eine implizite Konvertierung existiert). Ein Nullzeiger bleibt ein Nullzeiger. Bei reinterpret_cast ist das nicht unbedingt so (in der Praxis wohl schon, aber egal...)
audacia schrieb:
camper schrieb:
Falls der Compiler alles richtig macht, spielt das Layout keine Rolle - dieser Code hat in jedem Falle wohldefiniertes Verhalten. Wir können gerne diskutieren, dass die Compilerunterstützung hier möglicherweise nicht immer genügt.
Das heißt, der Standard definiert das erwartete Verhalten für diese Situation? Dann wird es ja dringend Zeit für ein paar Bugreports.
Möglicherweise auch nur eine Frage von Compilerschaltern. Visual C++ etwa implementiert Zeiger auf Memberfunktionen per default nicht korrekt (aus (Speicher-)Effizienzgründen), dies könnte in diesem Falle zu Fehlern führen (schau dir die /vmx-Schalter an). Das ist dann allerdings ein Problem einer nicht standardkonformen Erweiterung.
-
byto schrieb:
Ich zitiere mal aus dem wohl bekanntesten Werk "Entwurfsmuster" der Gang of Four.
Da hier auch Seitenzahlen verlangt wurden, bitte sehr:
Seite 23-24: Klassen- versus Schnittstellenvererbung, Prgrammieren auf eine Schnittstelle hin, nicht auf eine Implementierung
Seite 26ff.:Vererbung versus Komposition, Delegation
-
Danke! Und, war es so schwer die Quelle zu nennen?
Also!
Übrigens sind die Ratschläge auch in jedem anderen C++-Buch a la "Effektiv C++ programmieren" nachzulesen. Ändert nichts daran, das wenn ich pure virtual classes brauche, diese benutze. Ist mir egal obs in anderen Sprache mit dem Schlüsselwort interface gelöst wird. In C++ habe ich das Schlüsselwort interface nicht... und jetzt?
-
java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Sehe ich nicht so.
Eine Klasse in den gängigen Sprachen ist zwei Dinge: eine Bauvorschrift für Objekte und ein Typ. Der Typ dient dazu, dass das Typsystem zur Compilezeit sicherstellen dass keine Typfehler auftreten (da C++ ein Schwaches Typsystem hat ist das natürlich nicht unbedingt konsequent möglich).Gehen wir mal von einer hypothetischen Sprache aus, die gewisse Ähnlichkeiten zu Java/C++/... hat:
class Foo { def methode() : void { ... } def weitereMethode() : void { ... } def undNocheine() : void { ... } }
Foo ist eine Bauvorschrift für Objekte die folgenden Typ haben:
type { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; }
Natürlich ist jees Objekt, dass nach der Vorschrift Foo erstellt wurde Subtyp der Typen
type { methode : void -> void; weitereMethode : void -> void; }
type { methode : void -> void; undNocheine : void -> void; }
type { weitereMethode : void -> void; undNocheine : void -> void; }
type { methode : void -> void; }
type { weitereMethode : void -> void; }
type { undNocheine : void -> void; }
und
type {}
Jetzt kann ich soetwas machen:
bar : { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; } = Foo();
Natürlich kann man Typen Namen geben, wodurch das ganze etwas übersichtlicher wird:
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; } bar : Bar = Foo();
In den üblichen Sprachen ist der Name der Klasse gleichzeitig auch ein Name für den Typ der Objekte, die durch die Bauvorschrift erstellt werden.
foo : Foo = Foo();
Foo wird hier einmal als Typ verwendet und einmal als Bauvorschrift.
Ich kann eine weitere Bauvorschrift schreiben, die Objekte des selben Typserstellt:
class Baz { def methode() : void { ... } def weitereMethode() : void { ... } def undNocheine() : void { ... } }
Auch wenn die Implementierung verschieden ist, ist der Typ der erstellbaren Objekte der selbe.
type { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; }
Wenn ich jetzt eine Funktion schreiben will, die zu ihrer Asführung vorrausetzt, dass ihr Argument eine bestimmte Methode besitzt, dann könnte das so aussehen:
def funktion(argument : { methode : void -> void }) { ... }
Da der Typ des Arguments ein Subtyp des Typs der Objekte ist, die durch Foo und die durch Baz erstellt werden, kann ich Objekte die nach beiden Bauvorschriften erstellt wurden an "funktion" übergeben
Soweit so gut. Bisher bin ich von strukturellem Subtyping ausgegangen. Java/C++ verwenden hingegen ein nominatives Typsystem. Die Typen haben quasi zusätzlich ein Namen. Damit ein Typ als Subtyp eines anderen gilt muss nun explizit angegeben werden, dass der Typ mit diesem Namen Subtyp des Typs mit jenem Namen ist.
named_type X { methode : void -> void } named_type Y { methode : void -> void } named_type Z { methode : void -> void weitereMethode : void -> void }
Obwohl X und Y beide strukturell gleich sind gelten sie als inkompatibel. Auch Z gilt nicht als Subtyp von X oder Y.
named_type X2 { methode : void -> void } named_type Y2 is_subtype_of X2 { methode : void -> void }
Erst durch diese explizite Angabe ist Y2 Subtyp von X2.
Mein Kleines Beispiel mit der Funktion von oben funktioniert in Mainstreamsprachen nicht. Wie gesagt haben sie ausschließlich nominative Typsysteme. Typen müssen also Namen haben
named_class NamedFoo { def methode() : void { ... } } named_type Dummy { methode : void -> void } def funktion(argument : Dummy) { ... } funktion(Foo()); // Fehler
In einem nominativen Typsystem funktioniert das Beispiel nicht, weil nicht explizit angegeben wurde, dass der Typ Foo Subtyp des Typs Dummy ist; die Bauvorschrift Foo erstelt aber Objekte des Typs Foo.
named_type Dummy { methode : void -> void } named_class NamedFoo is_subtype_of Dummy { def methode() : void { ... } } def funktion(argument : Dummy) { ... } funktion(NamedFoo()); // OK
Dummy und NamedFoo beschreiben Typen der selben Struktur. Deswegen kommt man schnell zu dem Schluss einfach sowas zu schreiben:
named_class NamedFoo { def methode() : void { ... } } def funktion(argument : NamedFoo) { ... } funktion(NamedFoo()); // OK
In meinem anfänglichen Beispiel wo ich noch von einem strukturellen Typsystem ausgegangen bin konnte ich eine weitere Klasse Baz schreiben, und dann Objekte die nach dieser Bauvorschrift erstellt wurden ebenfalls an die Funktion übergeben.
Genau aus diesem Grund führt man den zunächst nutzlos erscheinenden Typ Dummy ein, damit man auch anderen die Chance gibt Objekte mit anderem Verhalten zu erstellen, und sie an funktion zu übergeben.
-------------
Interfaces sind einfach Typen, Klassen sind Bauvorschriften und Typen, "Abstrakte Klassen" sind unvollständige Bauvorschriften und Typen.
Vererbung ist eine Art Copy&Paste von Bauvorschriften und gleichzeitig das Herstellen einer (nominativen) Subtypbeziehung.
Das ist zumindest eine Betrachtungsweise des ganzen.
-
tfa schrieb:
Shade Of Mine schrieb:
java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Darf ich mal fragen, mit welcher Art Softwareentwicklung du dich beschäftigst?
Benutzt du Java in größeren Projekten, oder eher nur C++? Kennst du Applikationframeworks wie z.B. Spring? EJBs etc.?Ich benutze die Tools die ich effektiv sind. Sei es jetzt Java, .NET, C, C++, PHP oder VB. (aktuell gehen die Projekte wieder Richtung C++ (MFC) vorher aber laengere Zeit fast nur Java).
Nur weil ich Tools effektiv einsetzen kann heisst es noch lange nicht dass ich sie gut finden muss. Und EJB, bitte. Ist es dein ernst dass EJB gutes Design ist? Praktisch - keine Frage, aber die ganzen getter/setter - das tut einem OOD'ler einfach nur weh...
Ich setze zB die MFC oft ein. Ist sie schoen designed? Nein. Macht es Spass mit ihr zu programmieren? nein. Ist sie effektiv? Ja.
Und nur das zaehlt in der grossen weiten welt - aber gerade hier im Forum kann man durchaus mal den Leuten etwas beibringen - naemlich dass Java nicht das non plus ultra ist.
Java hat viele Sachen probiert - zB checked Exceptions. Manche Sachen funktionieren gut, manche weniger gut. Aber man kann mit Java arbeiten ohne dass sich einem der Magen umdreht. Aber Java bemueht sich einfach zu sehr den Programmierer am schlechten Code schreiben zu hindern (was natuerlich nicht klappt) dass einige schoene Designs einfach nicht moeglich sind.
Diese Trennung Interface/Klasse ist super fuer anfaenger, denn wie oft haben wir schon gesehen dass man eine Klasse Autos von Auto und std::vector ableiten will, anstatt einen std::vector<Auto> zu erstellen? Java verbietet soetwas by design. Super Sache ansich. Aber wenn man mal tiefer einsteigt, dann beengt einen Java. Weil ich will nicht Autos von vector und Auto erben lassen, ich will nicht von mehreren konkreten Klassen erben (meistens jedenfalls nicht) - ich will ja von interfaces erben - nur eben nicht von Java Interfaces.
AbstractFoo, DefaultFoo, IFooable - in anderen Sprachen habe ich dafuer genau eine Klasse und habe kein bisschen komplexere hierachien.
Was euch so verwirrt ist, dass fuer mich ein interface mehr ist als das was Java als interface definiert.
Wenn ich mir die 2 Links zu dem GoF Buch ansehe, dann stelle ich fest dass du entweder das Buch nicht verstanden hast, oder das was ich sage.
Ich sehe da nirgendwo die Java Definition eines Interfaces. Die verlinkten stellen gehen sogar einen Schritt weiter als ich hier aussagen wollte.
Schnittstellenvererbung sagt aus wann ein Objekt anstelle eines anderen verwendet werden kann
Ich nenne es hier Konzepte - um mich von den Java Interfaces zu distanzieren. Aber Namen sind Schall und Rauch. Nur weil eine Klasse abstrakt ist, heisst es noch lange nicht dass sie nicht verhalten haben kann. Die Schnittstelle wird definiert und diese Schnittstelle darf ruhig verhalten haben.
Implementierungsvererbung wie es dort so schoen heisst, ist die Implementierung eines Objektes mit Hilfe eines anderen.
Ich sehe da nicht den kleinsten widerspruch zu dem was ich bisher gesagt habe. Ich will ja nicht einen vector mit hilfe von serializable implementieren - ich will dass vector die schnittstelle serializable definiert (und nebenbei will ich ein vernuenftiges default verhalten haben, dass ich ueberschreiben kann wenn noetig).
byto schrieb:
Eigentlich dachte ich nicht, dass man das Offensichtliche auch noch belegen muss, aber bitte. Ich zitiere mal aus dem wohl bekanntesten Werk "Entwurfsmuster" der Gang of Four. In Abschnitt 1.6.4 werden zwei Prinzipien kurz, knapp und präzise folgendermaßen zusammengefasst:
1.) Programmiere auf Schnittstellen hin, nicht auf eine Implementierung
2.) Ziehe Objektkomposition der Klassenvererbung vorUnd wieder jemand der es nicht verstanden hat. Ich habe nie etwas gegen Schnittstellen gesagt - ich rede gegen die Java Definition einer Schnittstelle.
Edit: Bruce Eckel schreibt ähnliches in Thinking in Java, genauso wie Martin Fowler. Aber da hättet Ihr sicherlich wieder Kritik geübt, weil es ein Java-Buch ist (auch wenn sich der entsprechende Teil auf OOP im Allgemeinen bezieht). Das o.g. Buch benutzt aber C++ und Smalltalk als Referenzsprachen für die Codebeispiele, insofern bin ich nun auf weitere Kommentare gespannt.
In einem Java Buch geht man auf Java ein, in einem C++ Buch auf C++.
Du wirst in keinem guten C++ Buch die Java Definition von Interfaces finden - in Smalltalk Buechern schon garnicht (Smalltalk hat nichtmal Interfaces als Konzept). Dennoch ist Smalltalk eine wunderschoene Sprache wie viele OOP'ler dir gerne bestaetigen. Obwohl Smalltalk keine Java Interfaces bietet.
Auch bin ich sofort dabei wenn es um komposition statt vererbung geht, aber komposition ist nicht immer moeglich. zB ein DefaultFoo mittels komposition zu implementieren ist einfach falsch. Die Sprache kann einen dazu zwingen, aber es ist design technisch eine katastrophe. Ich verliere ja den kompletten zugriff, kann mich in keine algorithmen reinhaengen, keine werte setzen, garnichts.
Erklaer mir bitte einfach nur folgenden Punkt:
struct Serializable { virtual string serialize() { return reflection.getAllMemberData(this); } }; class Vector : public Serializable, Container { //... };
Warum ist das schlechtes Design?
Oder wenn dir Serializable nicht gefaellt - nimm Movable wie es zB MOJO bietet.
MOJO ist ein interessanter Ansatz aus C++ Sicht, aber betrachten wir einmal nur das Design:Ich definiere eine Klasse als Movable indem ich sie von Movable ableite. Movable definiert nichts anderes als eine Schnittstelle, enthaelt aber Code um die Klasse wirklich Movable zu machen. Ansonsten waere es ja ein Flag (wie das Java Serializable und sobald wir Interfaces als Flags verwenden, verlieren wir Polymorphie).
Warum ist es nun schlecht einen Movable vector zu haben? Ich wuerde zB von std::vector erben und von movable. Leider haben wir keine static vererbung daher ist es etwas gefaehrlich weil vector keine gute basisklasse ist, aber wir koennen uns ja vorstellen dass dies dennoch der Fall ist.
Eine Movable und Serializable Klasse zu haben, finde ich auch nicht abwegig. In Java nicht elegant moeglich, weil ich dann Copy&Paste machen muesste. Denn DefaultSerializable kann kein Member sein - hier brauche ich Vererbung. Also haben wir ploetzlich folgenden Code:
struct Serializable { virtual string serialie(); //use: "return reflection.getAllMemberData(this);" }; class Vector : public Serializable, Vector { public: void string serialize() { return reflection.getAllMemberData(this); } };
Oder vielleicht noch schoener mit einem DefaultSerializableBehavior Objekt?
struct DefaultSerializableBehavior { string serialize(Object* o) { return reflection.getAllMemberData(o); } }; class Vector : public Serializable, Vector { private: DefaultSerializeableBehavior s; public: void string serialize() { return s.serialize(this); } };
Von einem OOD standpunkt aus, ist keins davon wirklich schoen.
funktionieren tun sie alle, aber warum ist die kuerzeste und direkteste variante die schlechteste?Der Grund warum man gegen Mehrfachvererbung von konkreten Klassen ist, ist der dass die komplexitaet des Codes ansteigt. Das ist aber mit meinen "Konzepten" kein bisschen mehr der Fall als bei Interfaces. Die Gefahr dass sich Funktionen ueberlagern ist genauso gegeben wie mit Interfaces. Wenn 2 unterschiedliche Interfaces den selben Namen fuer eine Methode verwenden, hat man sowieso ein Problem. Denn entweder ist das Design schlecht und es sollten 3 Interfaces oder 1 Interface sein oder aber die Methode hat unterschiedliche verhaltensdefinitionen.
Und sonst? Welche Nachteile von mehrfachvererbung habe ich denn bei meinen Konzepten die man mit Java Interfaces nicht hat? Eine ganz einfache Frage.
-
Helium schrieb:
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; } bar : Bar = Foo();
Und warum genau klappt das ganze nicht mehr, wenn wir Bar erweitern?
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; hash : void -> int { return someHash(); } }; var bar : Bar = Foo();
Foo kann sich jetzt aussuchen ob es hash implementiert oder ob der default hash reicht.
hash kann noch einen Schritt weiter gehen und auf methoden basieren die in Bar abstrakt sind:
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; call_all : void -> void { methode(); weitereMethode(); undNocheine(); } };
Bar bleibt ein Typ, es ist keine vollstaendige Bauvorschrift. Es definiert lediglich Rahmenbedingungen. Es gibt lediglich einen weiteren Zustand:
Bar verlangt, dass methode implementiert werden muss. Wenn methode fehlt, ist das Objekt nicht vom Typ Bar.
call_all geht jetzt den Weg zu sagen: Alles was ein Bar ist, hat eine call_all methode. Wie diese methode implementiert ist, ist mir egal - aber ich kann fuer jedes Bar Objekt call_all ausfuehren.Das Problem hierbei ist, dass die Syntax sehr limitiert ist. Man koennte call_all ja auch als freie Funktion schreiben, nur ist C++ und Java zu dumm fuer soetwas, weil es kein dispatching kann.
void call_all(Bar& b) { b.methode(); b.weitereMethode(); b.undNochEine(); }
Wenn wir nun vernuenftiges dispatching zur laufzeit haetten, koennten wir call_all fuer einen subtyp von Bar so schreiben:
void call_all(Baz& b) { b.methode(); b.weitereMethode(); b.undNochEine(); b.undEineVierte(); }
Und schon braeuchten wir die ganze diskussion nicht mehr fuehren
-
Jetzt beschreibst du "Traits" (nicht C++-Traits, sondern was der unbekannte Rest der Programmiererwelt als Traits bezeichnet). Ich glaube das erste Paper kommt von Martin Odersky (?).
Aber das sind definitv keine reinen Typinformationen mehr, mit deren Hilfe formal der Code auf Typfehler überprüft werden kann.
-
Helium schrieb:
Jetzt beschreibst du "Traits" (nicht C++-Traits, sondern was der unbekannte Rest der Programmiererwelt als Traits bezeichnet). Ich glaube das erste Paper kommt von Martin Odersky (?).
Aber das sind definitv keine reinen Typinformationen mehr, mit deren Hilfe formal der Code auf Typfehler überprüft werden kann.Das Problem ist nunmal diese Konzepte in die Syntax von Sprachen wie C++ und Java einzubauen.
Ich waere sofort dafuer zu sagen, dass ein Typ lediglich definiert was alles da sein muss - und man Verhalten problemlos ueber freistehende Funktionen implementieren kann/soll.
Nur da es das leider nicht spielt, muss man irgendwo einschnitte machen. Wie gesagt ein
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; } void call_all(Bar b) { b.methode(); b.weitereMethode(); b.undNochEine(); } bar : Bar = Foo();
waere mir am liebsten.
Nur C++, Java und die meisten Sprachen bieten mir diese Moeglichkeit nicht. Was kann man also tun um das call_all dennoch zu bekommen?
-
Shade Of Mine schrieb:
ich will nichts von implementierungsdetails wissen - ich will eine logische begründung haben wie du ein serializable implementieren willst. In Java ist das nicht ohne hacks möglich. Und sobald man hacken muss ist das design imho fehlerhaft. Und man kann nichtmal alles hacken - ich kann zB meine serialisierten daten nicht verschlüsseln oder mich anders serialisieren als java es will.
Was brauchst du da für hacks?
Musst doch nurprivate void writeObject(java.io.ObjectOutputStream out) throws IOException
und
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
implementieren und schon kannst du da z.B. verschlüsseln
-
wobei die eingebaute serialisierung in java ein ganz finsteres thema ist. das ist mir zu sehr quer gehackt und voller magie.
mehrfachvererbung wurde aus java nicht entfernt, weil man sie unter keinen umständen benötigt. es gibt sicher fälle, in denen mehrfachvererbung für ein problem eine recht gute lösung darstellt. aber mehrfachvererbung kommt mit den kosten komplexeren codes. dokumentationen müssen in solchen systemen viel regeln, was durch abwesenheit von mehrfachvererbung schlicht nicht passieren kann.
programmiersprachen haben sich immer weiter entwickelt, um dem entwickler werkzeuge an die hand zu geben, auch große projekte wartbar und durchschaubar zu halten. objektorientierung ist da ein ganz großes thema. es gibt durchaus fälle, in denen ein simples ansi-c programm mit ein paar globalen variablen und methoden ein beliebiges problem schnell, effizient und sogar elegant löst. aber sobald es komplexer wird, hat man liebend gern die möglichkeiten von OO zur verfügung.
als java entwickelt wurde hatte man bereits die erfahrung gemacht, dass mehrfachvererbung stetige ursache von programmierfehlern ist. wiegt man das mit dem nutzen auf, liegt es nahe, dieses konzept schlicht nicht zu übernehmen. ebensowenig, wie java globale namensräume oder pointerarithmetik kennt. viele konzepte, die c/c++ entwickler liebgewonnen hatten, aber wie gesagt fehlerträchtig waren.
wenn man sich darauf einlässt, kommt man damit auch wunderbar zurecht.wenn ich mir code anderer leute angucke sehe ich häuftig schlechten code. egal in welcher sprache. aber ich muss mir wesentlich seltener bei java code die hände über dem kopf zusammmenschlagen, als bei so manch vermurkstem c++ konstrukt. und das verdankt die sprache dem korsett, welches einem entwickler angelegt wird.
-
Shade Of Mine schrieb:
Nur weil eine Klasse abstrakt ist, heisst es noch lange nicht dass sie nicht verhalten haben kann. Die Schnittstelle wird definiert und diese Schnittstelle darf ruhig verhalten haben.
[...]
Und wieder jemand der es nicht verstanden hat. Ich habe nie etwas gegen Schnittstellen gesagt - ich rede gegen die Java Definition einer Schnittstelle.
Vielleicht erklärst du uns mal, was DU unter einer Schnittstelle verstehst? Offenbar nicht das, was die Allgemeinheit darunter versteht. Denn ein Java-Interface entspricht genau dem, was eine OO-Schnittstelle im Allgemeinen ausmacht.
-
Ich behaupte nicht, dass Java der Weisheit letzter Schluss in Sachen Objektorientierung ist (C++ noch viel weniger). Allerdings sehe ich die Probleme eher woander als im Schnittstellenkonzept.
Andere z.B. dynamische Sprachen wie Python oder Ruby (oder auch das schon erwähnte Smalltalk) lösen dieses Problem anders, z.B. durch Mix-Ins, die sicherlich eleganter sind als Mehrfachvererbung. Aber es ist eben ein anderes Konzept; Typen sind hier irrelevant, Interface unbekannt.
Shade Of Mine schrieb:
Aber Java bemueht sich einfach zu sehr den Programmierer am schlechten Code schreiben zu hindern (was natuerlich nicht klappt) dass einige schoene Designs einfach nicht moeglich sind.
Diese Trennung Interface/Klasse ist super fuer anfaenger, denn wie oft haben wir schon gesehen dass man eine Klasse Autos von Auto und std::vector ableiten will, anstatt einen std::vector<Auto> zu erstellen? Java verbietet soetwas by design. Super Sache ansich. Aber wenn man mal tiefer einsteigt, dann beengt einen Java.
Das hört man leider ziemlich oft. Bedenke, dass Software meistens in Teams entwickelt wird und nicht
immer alle Teammitglieder sind solche Cracks, dass sie für dich elegante „Expertenlösungen“ verstehen
und warten können. Noch schlimmer, wenn der Experte irgendwann das Team verlässt und keiner
mehr seine Hinterlassenschaften durchschaut. Am schlimmsten, wenn der Experte nur von sich glaubt,
ein solcher zu sein und die Konsequenzen seiner Lösung nicht durchschaut. Die Erfinder von Java
haben sich nicht ohne Grund dazu entschlossen, einige „Killer-Features“ nicht anzubieten. Siehe auch:thordk schrieb:
wenn ich mir code anderer leute angucke sehe ich häuftig schlechten code. egal in welcher sprache. aber ich muss mir wesentlich seltener bei java code die hände über dem kopf zusammmenschlagen, als bei so manch vermurkstem c++ konstrukt. und das verdankt die sprache dem korsett, welches einem entwickler angelegt wird.
Was euch so verwirrt ist, dass fuer mich ein interface mehr ist als das was Java als interface definiert.
Wenn ich mir die 2 Links zu dem GoF Buch ansehe, dann stelle ich fest dass du entweder das Buch nicht verstanden hast, oder das was ich sage.Letzteres. Definitiv.
Die Schnittstelle wird definiert und diese Schnittstelle darf ruhig verhalten haben.
Implementierungsvererbung wie es dort so schoen heisst, ist die Implementierung eines Objektes mit Hilfe eines anderen.
Ich sehe da nicht den kleinsten widerspruch zu dem was ich bisher gesagt habe. Ich will ja nicht einen vector mit hilfe von serializable implementieren - ich will dass vector die schnittstelle serializable definiert (und nebenbei will ich ein vernuenftiges default verhalten haben, dass ich ueberschreiben kann wenn noetig).
Ich glaube unsere Interpretation der Aussagen im GoF-Buch unterscheiden sich ziemlich -- ums mal vorsichtig auszudrücken.
Erklaer mir bitte einfach nur folgenden Punkt:
…
Von einem OOD standpunkt aus, ist keins davon wirklich schoen.
funktionieren tun sie alle, aber warum ist die kuerzeste und direkteste variante die schlechteste?
…Zu deinem serializable Vector Bespiel:
Wie würdest du mit Mehrfachvererbung folgendes lösen: Du hast Vektoren, die sich in einen
String serialisieren sollen, andere in XML oder in eine Binärform oder in eine Datenbank
(nenn es meinetwegen nicht serializable sondern persistable).
Hast du dann 1000 Vektor-Klassen wie StringSerializableVector, XMLSerializableVector, BinarySerializableVector, SqlSerializableVector, SqlOracleSerializableVector, SqlOracleVersion8SerializableVector, usw.?? Wenn du jetzt noch ein Set und eine Map
mit den gleichen serializable-Funktionalitäten haben willst, dann multipliziert sich die Anzahl Klassen nochmal.
-
Shade Of Mine schrieb:
Wie gesagt ein
type Bar = { methode : void -> void; weitereMethode : void -> void; undNocheine : void -> void; } void call_all(Bar b) { b.methode(); b.weitereMethode(); b.undNochEine(); } bar : Bar = Foo();
waere mir am liebsten.
Nur C++, Java und die meisten Sprachen bieten mir diese Moeglichkeit nicht. Was kann man also tun um das call_all dennoch zu bekommen?
Was soll das bitte sein? Soll call_all(Bar b) eine Funktion im Typ Bar sein?
Es ist doch nun mal so dass man zwei Dinge unterscheidet: Schnittstelle (also die Typinformation) und die Implementierung der Schnittstelle (also die konkrete Klasse). Java bietet dir halt die Möglichkeit, mit dem Java-Interface explizit nur den Typ zu spezifizieren. Klassen spezifizieren auch einen Typ, aber liefern auch direkt die Implementierung. Abstrakte Klassen sind eine Mischung aus reiner Schnittstellenspezifikation und Implementierung.Was genau fehlt Dir daran? Was genau findest Du schlecht? Was genau möchtest Du besser machen?
-
tfa schrieb:
Zu deinem serializable Vector Bespiel:
Wie würdest du mit Mehrfachvererbung folgendes lösen: Du hast Vektoren, die sich in einen
String serialisieren sollen, andere in XML oder in eine Binärform oder in eine Datenbank
(nenn es meinetwegen nicht serializable sondern persistable).
Hast du dann 1000 Vektor-Klassen wie StringSerializableVector, XMLSerializableVector, BinarySerializableVector, SqlSerializableVector, SqlOracleSerializableVector, SqlOracleVersion8SerializableVector, usw.?? Wenn du jetzt noch ein Set und eine Map
mit den gleichen serializable-Funktionalitäten haben willst, dann multipliziert sich die Anzahl Klassen nochmal.Selbstverständlich wäre die zu implementierende serialize-Methode unabhängig von der Art der Ein- bzw. Ausgabe. Daher reicht es, von Serializable abzuleiten.
Sowas muss doch auch in Java machbar sein.
-
.filmor schrieb:
Sowas muss doch auch in Java machbar sein.
Ist es, wie schon mehrfach angesprochen. Nur eben nicht überImplementationsvererbung druch Mehrfachvererbung.
-
byto wech schrieb:
Was genau fehlt Dir daran? Was genau findest Du schlecht? Was genau möchtest Du besser machen?
Die unimplementierbarkeit von call_all().
Es ist einfach nicht möglich das in Java zu machen.
Der beste Ansatz wäre, freistehende Funktionen zu haben die das Verhalten definieren. Dazu müsste man aber weggehen von dem Klassenkonzept. Dann hätte man eine Menge Probleme nicht und diese ganze Diskussion hier wäre unnötig.
Aber wir haben Klassen und müssen damit leben. Klassen sind furchtbar einnengend. Und Java Interfaces engen noch mehr ein. Die Idee einer reinen minimalen Schnittstelle ist ja nicht schlecht, nur in Sprachen wie Java und C++ nicht verwendbar.
Man braucht sich nur Serializable ansehen. Es ist in Java nicht ohne Hack implementierbar. Das muss einem doch zu denken geben, oder?
-
tfa schrieb:
.filmor schrieb:
Sowas muss doch auch in Java machbar sein.
Ist es, wie schon mehrfach angesprochen. Nur eben nicht überImplementationsvererbung druch Mehrfachvererbung.
Hab ich dich nur falsch verstanden oder hast du es tatsächlich geschafft, eine Zeile eines aus zwei Zeilen bestehenden Posts ihres Kontextes zu berauben?
Mir ging es darum, dass es auch in Java machbar sein muss, dass serialize unabhängig von der Art der Serialisierung (String, XML, was auch immer) nur einmal implementiert wird.
-
Braucht man Mehrfachvererbung?
-
Shade Of Mine schrieb:
Man braucht sich nur Serializable ansehen. Es ist in Java nicht ohne Hack implementierbar. Das muss einem doch zu denken geben, oder?
Das behauptest du jetzt schon zum zweiten mal, dann verrate doch mal bitte wo du einen Hack brauchst um in Java Serializable zu implementieren. Was kannst du den nicht machen? Wo schränken dich da die Java Interfaces ein?
-
wathacks schrieb:
Shade Of Mine schrieb:
Man braucht sich nur Serializable ansehen. Es ist in Java nicht ohne Hack implementierbar. Das muss einem doch zu denken geben, oder?
Das behauptest du jetzt schon zum zweiten mal, dann verrate doch mal bitte wo du einen Hack brauchst um in Java Serializable zu implementieren. Was kannst du den nicht machen? Wo schränken dich da die Java Interfaces ein?
Das ist ein Scherz oder?
Schau dir mal an wie writeObject und readObject integriert wurden.Die JVM hat eine extra spezialbehandlung eingeführt. Etwas dass Clientcode nicht machen kann. Die Benutzung ist zwar super einfach - aber die Technik dahinter - eben _wegen_ den Java Interfaces ein Horror.