Als Java-Entwickler noch C/C++ dazulernen: Chancenlos?
-
Xin schrieb:
marco.b schrieb:
Xin schrieb:
ich spielte durchaus schon mit dem Gedanken, Aggregationen komplett abzuschaffen: weniger Boilerplate und zwangsläufig scharfe Typdefinitionen. Das will man doch!?
Wenn ich richtig verstanden habe, wie du Vererbung einsetzt, koppelst du aber auch stärker. Stichwort als Beispiel: Inversion of Control.
Wieso kopple ich stärker?
Nehmen wir an, du hast eine abstrakte Basisklasse "Log", welche lediglich rein virtuelle Methoden besitzt, etwa log(int code, string message).
Nun gibt es die Subtypen
SystemEventLog : Log
FileLog : Log
TcpLog : Log
welche alle ein Anwendungsprotokoll hin zu verschiedenen Datenquellen implementieren.Wenn du nun keine Aggregation/Komposition sondern nur Vererbung verwendest und du in Klasse Worker ein Protokoll benötigst, hast du zwei Möglichkeiten:
a)
class Worker : Log { ... }
Wäre rein logisch offenbar falsch. Schließlich willst du kein Log implementieren, sondern eins verwenden. Ferner kannst du obige Implementierungen nicht wiederverwenden.
b)
class Worker: FileLog
Also das Erben von einer konkreten, implementierenden Klasse. Damit hast du aber eine fest Abhängigkeit gekoppelt.
Mit Aggregation könntest du dagegen einen Member vom Typ Log deklarieren, die konkrete Implementierung aber durch Polymorphie-Zauber dynamisch auflösen, etwa weil du Protokollkanäle dynamisch konfigurierbar halten willst. In Java/C# werden solche Aufgaben üblicherweise mit Dependency Injection Frameworks gelöst. Das klappt aber bei deiner Vererberei nicht.
-
Moin marco!
Danke für den erklärenden, nicht abfälligen Tonfall. So macht der Austausch jedenfalls Spaß.
marco.b schrieb:
Mit Aggregation könntest du dagegen einen Member vom Typ Log deklarieren, die konkrete Implementierung aber durch Polymorphie-Zauber dynamisch auflösen, etwa weil du Protokollkanäle dynamisch konfigurierbar halten willst. In Java/C# werden solche Aufgaben üblicherweise mit Dependency Injection Frameworks gelöst. Das klappt aber bei deiner Vererberei nicht.
Eine schöne Problemstellung, für die ich sogar bevorzugt Vererbung wählen würde!
Ich kann in C++ keinen Member vom Typ Log deklarieren, da Log ja eine abstrakte Basisklasse ist.
Das geht in Java, aber da wird ein "Referenztyp" auch als "Log " definiert. Entsprechend muss ich ebenfalls einen (Log) nehmen, bzw. wenn ich das ausschließlich zur Initialisierungszeit für immer festlegen möchte, dann habe ich in C++ auch die Möglichkeite ein (Log &) zu nehmen. Damit wäre die Kopplung frei bis zur Initialisierung. Das würde aber nicht der Java-Referenz entsprechen und warum sollte ich mich festlegen, Logger niemals wechseln zu können!?
Möchte ich das Problem wie von Dir beschrieben lösen, würde ich einen LogForwarder definieren, den ich nicht von Log abgeleiten würde, aber inline das gleiche Interface besitzt und der einen Member vom Typ (Log besitzt. Nochmal: Dass ich mehr Ableitungen verwende, bedeutet nicht, dass ausschließlich ableite und ich keine Member verwende! Ich verwende sie vor allem da, wo ich sie verstecken will. Hier muss ich einen Member nehmen, weil ich in C++ ja gar nicht von (Log ableiten kann. Ich kann diese Funktionalität in C++ also anders gar nicht formulieren.
Das ist - wie ich schon sagte - mehr Arbeit, um die Typen zu definieren, hat aber den Vorteil (und deswegen würde ich es so implementieren), dass ich die Frage, ob ich überhaupt Loggen kann (Member ist nullptr) einmalig im LogForwarder klären kann und nicht in jeder Klasse erst den Member fragen muss, ob er Null ist. Ich logge einfach, wenn kein Logger da ist, muss die Frage, ob wirklich geloggt wird, in LogForwarder geklärt. Ich bin also im Gegensatz zu einem Member nicht in Gefahr, mal die Frage zu vergessen und ein SegFault zu provozieren und falls ich doch auf einem SegFault stoße, dann weiß ich, dass das Problem ausschließlich in der Klasse LogForwarder zu suchen ist und ich garantieren kann, dass dieser Bug Problem danach für das komplette Programm erledigt ist.
Du wolltest ein Log als Member nehmen. Du schreibst
if( logmember ) logmember->Log( "Bla" );
ich schreibe
Log( "Bla" );
Beides funktioniert gleichwertig. Bei mir wird die Frage inline dazugepackt, Du musst sie schreiben. Ich kann garantieren, dass ich niemals auf logmember zugreife, wenn es null ist, Du musst sehr diszipliniert sein, um das niemals zu vergessen. Ich bin diszipliniert, mag mich aber nicht aus dem Fenster lehnen, bei so etwas eine Garantie auszusprechen, also typisiere ich eigentlich so ziemlich alles.
Nehme ich einen Member, bin ich verlockt, einfach ein Log * zu nehmen - es ist einfach weniger Arbeit. Um das Problem über Vererbung zu lösen MUSS ich mein Problem genau zu typisieren.
Um ebenfalls eine Garantie aussprechen zu können, müsstest auch Du eine Klasse LogForwarder als Member nehmen. Du hast also die gleiche Arbeit, die Forwarderklasse zu schreiben und schreibst dannlogmember.Log( "Bla" );
Alle Lösungen lösen das gleiche Problem. Nur Du schreibst mehr als ich und wer mehr schreibt, macht mehr Fehler.
-
Xin schrieb:
Ich kann in C++ keinen Member vom Typ Log deklarieren, da Log ja eine abstrakte Basisklasse ist.
Natürlich. Ich meinte Log*, tut mir leid ich habe eine erschreckend hohe C#-Affinität, die sich gern mal ungewollt zeigt.
Xin schrieb:
Nochmal: Dass ich mehr Ableitungen verwende, bedeutet nicht, dass ausschließlich ableite und ich keine Member verwende!
Auf den Fall bezog sich aber meine Argumentation. Du schriebst ja auf Seite 2, dir schon überlegt zu haben, auf Aggregation komplett zu verzichten. Das hast du ja relativiert.
Ich logge einfach, wenn kein Logger da ist, muss die Frage, ob wirklich geloggt wird, in LogForwarder geklärt.
Das ist natürlich ein berechtigter Einwand. In C# (jaja ich weiß...) würde ich das mit Dependency Injection und zusätzlich Contract Based Design lösen: Das Vorhandensein des Logs ist eine Invariante des Typs, respektive eine Vorbedingung der jeweiligen Methode.
In C++ oder alternativ, je nach Requirement, würde ich den Logger hingegen einmalig im Konstruktor auf nullptr prüfen und ihn dann ggf. auf eine Dummy-Instanz [nenne ich ganz gerne Blackhole Log] setzen, die die Log-Methoden implementiert und schlicht nichts tut. Das geht in Richtung des Null Object Patterns.
-
marco.b schrieb:
Xin schrieb:
Ich kann in C++ keinen Member vom Typ Log deklarieren, da Log ja eine abstrakte Basisklasse ist.
Natürlich. Ich meinte Log*, tut mir leid ich habe eine erschreckend hohe C#-Affinität, die sich gern mal ungewollt zeigt.
Kein Problem, es war ja abzusehen, was gemeint war.
marco.b schrieb:
Xin schrieb:
Nochmal: Dass ich mehr Ableitungen verwende, bedeutet nicht, dass ausschließlich ableite und ich keine Member verwende!
Auf den Fall bezog sich aber meine Argumentation. Du schriebst ja auf Seite 2, dir schon überlegt zu haben, auf Aggregation komplett zu verzichten. Das hast du ja relativiert.
C++ fehlt hier die Möglichkeit, dies auszuformulieren. Darum kann ich in C++ leider nicht auf Member verzichten.
Wie gesagt, ich arbeite an einer eigenen Sprache. Leider bin ich noch nicht ganz soweit, dass ich ernsthaft vererben kann, aber ich werde da durchaus mehr Experimente machen können als in C++ und syntaktisch meine Art der Programmierung deutlich stärker unterstützen, so dass das ganze hoffentlich auch offensichtlicher ist und sich hoffentlich auch die Konsequenzen herausarbeiten, die in C++-Quelltext nicht vollständig darstellbar sind.
Ziel ist hierbei durchaus aus Member komplett verzichten zu können.
marco.b schrieb:
In C++ würde ich den Logger hingegen einmalig im Konstruktor auf nullptr prüfen und ihn dann ggf. auf eine Dummy-Instanz [nenne ich ganz gerne Blackhole Log] setzen, die die Log-Methoden implementiert und schlicht nichts tut. Das geht in Richtung des Null Object Patterns.
Ebenfalls eine vom Design her absolut saubere Lösung, die - falls häufiger geloggt als verworfen wird - auch die bessere ist - schließlich würde dann häufig eine überflüssige Frage gestellt. Werden die Logeinträge häufiger verworfen als geloggt, würde ich meine Variante als die schnellere bevorzugen.
Auch hier sieht man wieder, dass es eben nicht darum geht, eine Lösung als die Perfekte zu verkaufen, es sind zwei Lösungen, die Vor- und Nachteile mit sich bringen und man eben von Fall zu Fall entscheiden muss, was angebrachter ist.
-
Mich würde interessieren, wie genau du vor hast, in so einem System, das auf Member komplett verzichten soll, Zustand zu repräsentieren...
-
dot schrieb:
Mich würde interessieren, wie genau du vor hast, in so einem System, das auf Member komplett verzichten soll, Zustand zu repräsentieren...
TypeStates!
-
dot schrieb:
Mich würde interessieren, wie genau du vor hast, in so einem System, das auf Member komplett verzichten soll, Zustand zu repräsentieren...
Ich erfinde nicht die Programmierung neu.
Es sieht nicht nach C++ aus, aber man kann den Aufbau schon ähnlich wie in einer C++-Klasse nachvollziehen.
Ich würde es eher als Perspektivenwechsel beschreiben oder als Reduktion aufs Wesentliche - inhaltlich ändert sich dabei genauso wenig, wie bei der Frage, ob man ableitet oder einen Member nimmt. Doch je weniger Fragen ich stelle, desto weniger Fragen muss der Entwickler beim Aufbau der Klasse beantworten.
Entsprechend wird die Definition von Datenstrukturen vergleichsweise sehr kurz.Wenn's fertig ist und funktioniert, werde ich es vorstellen, aber das wird noch dauern, schließlich bezahlt mich für die Sprache keiner.
-
Xin schrieb:
Zeus schrieb:
Xin schrieb:
Genauso mit der Mehrfachvererbung, wo alle nur vom das Diamant-Problem warnen und nur wenige die Logik der Mehrfachvererbung einfach annehmen und für ihre Zwecke nutzen. Dafür muss man sich damit aber halt auseinander setzen und das geht nicht, wenn man damit keine Erfahrungen sammeln darf.
Abgesehen davon würde ich mir wünschen du könntest es auch so darstellen.
Du kannst die Augen verdrehen, wie Du magst, aber ich habe aber kein Problem mit dem Diamanten. Ich kann den Diamanten nicht als Problem darstellen, wenn ich ihn auch nutzen kann. Der Diamant ist - wie jede Programmiertechnik - etwas was Vor- und Nachteile besitzt und damit muss man sich auseinander setzen.
Lieber Xin,
der Smily, der Kursiv- und der Unterstrichenen-Text sind nicht umsonst da, vielleicht hätte ich um es besser hervorzuheben etwas durchstreichen sollen, damit die Meldung besser rüber kommt. Ein Begriff und eine Konsequenz wurden markiert, der Kontext, den umgebenden Text, allerdings nicht. Ich weiß echt nicht, welchen übel Gesindel dir über den Weg gelaufen ist: der Anti-Java-Hype-, die Anti-MI-Einstellung *urgh*. Ob du, ich oder jemand anders ein Problem mit dem Diamant-Problem hat, ist bedeutungslos. Von Bedeutung ist die Anwendung der Mehrfachvererbung und die Verantwortung über das Diamant-Problem herr zu werden. Zur Verdeutlichung finde ich daher schon angemessen von ein Problem zu reden.
Xin schrieb:
Akzeptiert bitte auch, dass andere Entwickler andere Erfahrungen machen und jeder das Recht hat, seine Erfahrungen zu machen und daraus die Schlüsse zu ziehen, die jedem erlauben, seine Probleme so qualitativ wie möglich zu arbeiten.
Häh? Kann nicht an mich gerichtet sein, muss eine Massage an alle sein, oder so.
Xin schrieb:
Ein Forum sollte dazu dienen, über diese Schlüsse zu beraten, Hilfestellung oder Inspiration zu geben, sich auszutauschen.
Du blockst doch aktive. Meine Frage ist noch nicht beantwortet. Aber eigentlich muss du es auch nicht beantworten,..
Xin schrieb:
Aber weder hat hier einer das Recht, Erklärungen einzufordern, noch Vorgaben zu einer Problemlösung machen, schonmal gar nicht, wenn das Problem überhaupt nicht bekannt ist oder gar sich bzw. das Forum als die entscheidende Instanz für anderer Leute Programmierdesign darzustellen.
Häh? Kann nicht an mich gerichtet sein, muss eine Massage an alle sein, oder so.
Xin schrieb:
Genausowenig besteht das Recht, erst die Augen zu verdrehen und dazu Wünsche in dieser Form zu äußern.
Wie bitte oben hattest du mir die Freiheit gegeben und jetzt willst du mein Recht nehmen. Du weiß schon Meinungsfreiheit
-
Xin schrieb:
Das ist - wie ich schon sagte - mehr Arbeit, um die Typen zu definieren, hat aber den Vorteil (und deswegen würde ich es so implementieren), dass ich die Frage, ob ich überhaupt Loggen kann (Member ist nullptr) einmalig im LogForwarder klären kann und nicht in jeder Klasse erst den Member fragen muss, ob er Null ist. Ich logge einfach, wenn kein Logger da ist, muss die Frage, ob wirklich geloggt wird, in LogForwarder geklärt. Ich bin also im Gegensatz zu einem Member nicht in Gefahr, mal die Frage zu vergessen und ein SegFault zu provozieren und falls ich doch auf einem SegFault stoße, dann weiß ich, dass das Problem ausschließlich in der Klasse LogForwarder zu suchen ist und ich garantieren kann, dass dieser Bug Problem danach für das komplette Programm erledigt ist.
Das Null Object Pattern gibt es schon ewig. Das ist kein bisschen neu oder einzigartig.
-
Zeus schrieb:
Von Bedeutung ist die Anwendung der Mehrfachvererbung und die Verantwortung über das Diamant-Problem herr zu werden.
Zur Verdeutlichung finde ich daher schon angemessen von ein Problem zu reden.
Akzeptiert.
Allerdings ist dann Programmieren genauso ein Problem, wie auch Editor installieren oder Computer einschalten. Ich habe hier "Problem" als etwas verstanden, dass eine Herausforderung darstellt, nachdem man sich damit beschäftigt hat.Den "Smiley" finde ich dennoch sehr unpassend.
Zeus schrieb:
Häh? Kann nicht an mich gerichtet sein, muss eine Massage an alle sein, oder so.
Ist allgemein gehalten, schließt aber keinen aus.
Zeus schrieb:
Xin schrieb:
Ein Forum sollte dazu dienen, über diese Schlüsse zu beraten, Hilfestellung oder Inspiration zu geben, sich auszutauschen.
Du blockst doch aktive. Meine Frage ist noch nicht beantwortet. Aber eigentlich muss du es auch nicht beantworten,..
Frage? Die einzige Frage, die Du gestellt hast, war die mit Start und Endpunkt. Ich denke, es ist Dir durchaus selbst möglich von Line auf Strecke schließen zu können, zumal diese Ungenauigkeit überhaupt nichts mit dem dort diskutierten Thema zu tun hat.
Muss man da wirklich noch drauf eingehen?Shade Of Mine schrieb:
Das Null Object Pattern gibt es schon ewig. Das ist kein bisschen neu oder einzigartig.
Und? Habe ich derartiges behauptet? Ich habe es nur in gefragten Fall genutzt, weil es sich als sinnvoll aufdrängte, wohingegen die vorgeschlagene offensichtliche Lösung ein Problem provozierte.
Das Argument ist nicht, ein NOP zu implementieren, sondern genötigt zu werden, ein Problem lösen zu müssen, sich darüber Gedanken machen zu müssen und in diesem speziellen Fall es mit eine NOP abzusichern.
Damit wäre die Hälfte dieses Postings wieder für Blödsinn draufgegangen. Keiner liest mehr den Anfang, dafür kommen neue Leute rein und wir nehmen die übliche Endlosschleife. Wer ein beliebigen Unterbereich dieses Topics weiter diskutieren möchte, möge ein Passendes im entsprechenden Bereich eröffnen.