PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Sinn und Unsinn von Vererbung


Shink
2008-09-25, 09:53:16
Split aus dem "OOP mit C (ohne ++) Thread:
http://www.forum-3dcenter.org/vbulletin/showthread.php?t=432709

Monger


Was ist mit dem Rest - insbesondere Vererbung? Ist das auch irgendwie machbar?
OOP braucht keine Programmiersprachenunterstützung von Vererbung. Erstens ist die immer vermeidbar (und böse:biggrin:) und zweitens kann man immer noch Code kopieren.

HajottV
2008-09-26, 10:43:26
OOP braucht keine Programmiersprachenunterstützung von Vererbung.

Doch, sonst es ist es keine OOP. Vererbung ist das, was OOP von objektbasierter Programmierung unterscheidet.

Erstens ist die immer vermeidbar (und böse:biggrin:) und

Vererbung ist ganz sicher nicht böse, sondern ein - für große Software Systeme - extrem nützliches Feature.

zweitens kann man immer noch Code kopieren.

Das ist ja wohl nicht Dein Ernst?! :|

Gruß

Jörg

Monger
2008-09-26, 11:18:26
Vererbung ist ganz sicher nicht böse, sondern ein - für große Software Systeme - extrem nützliches Feature.

Shink hat zumindest damit Recht, dass die Bedeutung von Vererbung für objektorientierte Sprachen gerne überschätzt wird.
Vererbung kann böse Nebenwirkungen haben wenn erbende und vererbende Klasse nicht durch den selben Entwickler gepflegt werden.

Vererbung ist definitiv NICHT das wichtigste Feature von OOP. Kapselung ist vermutlich das wichtigste, dicht gefolgt von Abstraktion über Interfaces.

Gast
2008-09-26, 13:47:55
Vererbung ist definitiv NICHT das wichtigste Feature von OOP. Kapselung ist vermutlich das wichtigste, dicht gefolgt von Abstraktion über Interfaces.

Vererbung ist DAS Feature von OOP. Und selbst, wenn DU das vielleicht nicht bewusst verwendest, so sind alle mondernen Frameworks vollgespickt von Vererbung. Ich bin eigentlich wie HajottV auch sprachlos, wie man so einen Unsinn erzählen kann.

AtTheDriveIn
2008-09-26, 14:25:02
Doch, sonst es ist es keine OOP. Vererbung ist das, was OOP von objektbasierter Programmierung unterscheidet.


Sehe ich auch so.

OOP bedeutet:

-Datenkapselung

-Vererbung

-Polymorphismus


Auch wenn man einen Punkt als persönlich weniger wichtig ansieht, bedeutet ein Verzicht nicht das man dennoch objektorientiert programmiert.
Wie beispielsweise die JAVA API ohne Vererbung aussehen soll ist mir vollkommen schleiferhaft.

Gast
2008-09-26, 16:13:18
Wie beispielsweise die JAVA API ohne Vererbung aussehen soll ist mir vollkommen schleiferhaft.

Interfaces und Delegation.

Gast
2008-09-26, 17:26:01
Interfaces und Delegation.

Ja ne, is klar... Dazu müsste ja der Anwendungsentwickler jedes Implementierungsdetail (Grafikebene, IO Ebene usw.) vom darunterliegenden System wissen, weil er das ja sowieso wieder neu implementieren muss.

Monger
2008-09-26, 19:04:42
Wie beispielsweise die JAVA API ohne Vererbung aussehen soll ist mir vollkommen schleiferhaft.
Bei Java hat man auch dazugelernt. In den ältesten Frameworks wurde da sehr verschwenderisch damit umgegangen. Bei den neueren APIs hält man sich da auffallend zurück. Im .NET Framework wirst du selten mehr als vier Vererbungsebenen finden.
Natürlich gehört Vererbung zur OOP - aber deren Wert wird oftmals überschätzt. Interfaces sind WEIT wichtiger und gebräuchlicher als Vererbung.

Ja ne, is klar... Dazu müsste ja der Anwendungsentwickler jedes Implementierungsdetail (Grafikebene, IO Ebene usw.) vom darunterliegenden System wissen, weil er das ja sowieso wieder neu implementieren muss.
Interessanterweise zwingt GERADE Vererbung dem Anwender jede Menge Detailwissen auf. Er muss nämlich meist ziemlich genau wissen, wie eine Klasse intern arbeitet bevor er von ihr erben und Methoden überschreiben kann.
Das widerspricht dem Konzept der Kapselung. Viele Angriffsstrategien machen sich genau das zunutze, dass komplexere Klassen auf diese Weise eine unheimlich breite Angriffsfläche bieten.
Das Problem hat man mit Interfaces nicht.

Noch einmal: Vererbung ist nett und nützlich - aber bei weitem nicht so nützlich wie man denken könnte. Innerhalb des selben Packages können abstrakte oder private Klassen sehr nützlich sein - aber es ist kein Werkzeug für den alltäglichen Gebrauch. Für einen Anwendungsentwickler spielt Vererbung ohnehin eher eine untergeordnete Rolle.

Es ist deshalb eher irritierend, im Zusammenhang mit OOP immer gleich mit der Vererbung zu wedeln. Viele andere OOP Features sind weit wichtiger.

Shink
2008-09-26, 19:41:47
Doch, sonst es ist es keine OOP. Vererbung ist das, was OOP von objektbasierter Programmierung unterscheidet.
Vererbung unterscheidet zwar OOP von objektbasierter Programmierung. Das heißt aber nicht dass man ohne Programmiersprachenunterstützung von Vererbung keine OOP machen kann.

Vererbung ist ganz sicher nicht böse, sondern ein - für große Software Systeme - extrem nützliches Feature.
Interfacevererbung ja. Subclassing nach heutiger Ansicht eher nicht. Die Zeiten von Smalltalk-ähnlichen Libraries sind vorbei. Das ist zwar irgendwie schade aber irgendwie ist es eben... böse;D

Das ist ja wohl nicht Dein Ernst?! :|
Natürlich nicht.

Vererbung ist DAS Feature von OOP. Und selbst, wenn DU das vielleicht nicht bewusst verwendest, so sind alle mondernen Frameworks vollgespickt von Vererbung. Ich bin eigentlich wie HajottV auch sprachlos, wie man so einen Unsinn erzählen kann.
Ich denke das hat sich jeder schon mal gedacht. Aber hinterfrage es mal kritisch oder lies ein paar Bücher über modernes OO Design.

Gast
2008-09-26, 20:05:44
Im .NET Framework wirst du selten mehr als vier Vererbungsebenen finden.


Erzähle doch keinen Schwachsinn! Vornhin meinst du noch ohne Vererbung auszukommen und jetzt redest schon von vier Vererbungshierarchien, die du für nicht erwähnenswert nennst. Und natürlich gibt es auch viele Klassen mit mehr als vier Hierarchien (z.B. MenuStrip, DataGrid, DataGridView, Form, UserControl uvm.)


Natürlich gehört Vererbung zur OOP - aber deren Wert wird oftmals überschätzt. Interfaces sind WEIT wichtiger und gebräuchlicher als Vererbung.


Interfaces haben in erster Linie ein ganz anderes Einsatzgebiet. Bei Vererbung geht es - wie es der Name sagt - um's vererben.


Interessanterweise zwingt GERADE Vererbung dem Anwender jede Menge Detailwissen auf. Er muss nämlich meist ziemlich genau wissen, wie eine Klasse intern arbeitet bevor er von ihr erben und Methoden überschreiben kann.


Ja klar, selten so einen Schwachsinn gelesen. Also anstatt z.B. die OnPaint Methode zu überschreiben und den übergebenen Parameter (Graphics object) zu verwenden, möchtest also lieber eine komlette Forms Klasse nachprogrammieren, über PInvoke die GDI+ verwenden und noch mal die komplette Message Loop reimplementieren, mit allen Ereignissen usw. Wahnsinns geniale Idee, Monger!


Das widerspricht dem Konzept der Kapselung. Viele Angriffsstrategien machen sich genau das zunutze, dass komplexere Klassen auf diese Weise eine unheimlich breite Angriffsfläche bieten.
Das Problem hat man mit Interfaces nicht.


Dann hast du das Konzept der Kapselung einfach nicht verstanden. Gekapselt wird unterschiedliche Logik. Erweitert wird durch Vererbung. Wer garantiert dir denn, dass die Initiallogik bei jeder Interface Implementierungshierarchie genau so implementiert wird, wie es initial vorgesehen war? Das garantiert dir nämlich kein Interface.


Noch einmal: Vererbung ist nett und nützlich - aber bei weitem nicht so nützlich wie man denken könnte. Innerhalb des selben Packages können abstrakte oder private Klassen sehr nützlich sein - aber es ist kein Werkzeug für den alltäglichen Gebrauch. Für einen Anwendungsentwickler spielt Vererbung ohnehin eher eine untergeordnete Rolle.


Ahja, interessantes Geschwall. Aber das sagt eher etwas über deine Entwicklerfähigkeiten aus.

Monger
2008-09-26, 21:46:13
Erzähle doch keinen Schwachsinn! Vornhin meinst du noch ohne Vererbung auszukommen und jetzt redest schon von vier Vererbungshierarchien, die du für nicht erwähnenswert nennst.

Ich habe nie behauptet, dass ich ohne Vererbung auskomme(n will). Ich habe nur gesagt: sie wird überschätzt.


Ja klar, selten so einen Schwachsinn gelesen. Also anstatt z.B. die OnPaint Methode zu überschreiben und den übergebenen Parameter (Graphics object) zu verwenden, möchtest also lieber eine komlette Forms Klasse nachprogrammieren, über PInvoke die GDI+ verwenden und noch mal die komplette Message Loop reimplementieren, mit allen Ereignissen usw. Wahnsinns geniale Idee, Monger!

Zum einen löst man gerade solche Probleme gerne mit Events. Dann sind zusätzliche Renderereignisse halt Bestandteil dieses Objekts. Ein Objekt entsprechend flexibel zu gestalten, ist auch ne Kunst.

Und GERADE solche Methoden à la OnPaint zu überschreiben, geht gerne mal in die Hose. Da kommen dann gerne mal diese kaum diagnostizierbaren Darstellungsfehler her! ;)

Zum anderen sind grafische Oberflächen auch ein gewisser Sonderfall. Weder was ich da in Java noch in .NET gesehen habe, war von der Robustheit her so wirklich das Nonplusultra. Absolut nix im Vergleich zum restlichen Framework.
Das dort mit solchen halbseidenen Lösungen gearbeitet wird, ist eher Workaround denn Feature.

Wenn du tatsächlich in die Verlegenheit geraten solltest, so tief in den Eingeweiden des Grafiksubsystems rumzuwühlen, musst du halt auch SEHR genau wissen was du tust. Oder noch besser: du scheißt den Entwickler des Frameworks zusammen, was er sich denn bei so einem Mist gedacht hat. ;)


Dann hast du das Konzept der Kapselung einfach nicht verstanden. Gekapselt wird unterschiedliche Logik. Erweitert wird durch Vererbung.

Kapselung dient dazu, die Oberfläche eines Objekts so weit es geht zu reduzieren - um eine robuste und verlässliche API zu garantieren. Vererbung bricht eben diese Sicherheit ein gutes Stück weit auf. Eine Klasse so zu schreiben dass sie durch Vererbung nicht zerstört werden kann, ist ganz schön kompliziert.


Wer garantiert dir denn, dass die Initiallogik bei jeder Interface Implementierungshierarchie genau so implementiert wird, wie es initial vorgesehen war? Das garantiert dir nämlich kein Interface.

Ein Interface hat aber wenigstens keine Nebenwirkungen auf bestehenden Code. Mit einer ungeschickt/bösartig vererbten Klasse kannst du bestehenden Code brechen. Das kannst du mit einem Interface nicht.



Ahja, interessantes Geschwall. Aber das sagt eher etwas über deine Entwicklerfähigkeiten aus.
Weil ich gut gelaunt bin, übersehe ich diesen Kommentar nochmal.

Wie Shink schon gesagt hat: lies dir mal ein paar aktuelle OOP Bücher durch. Ich zieh mir sowas nicht aus den Rippen.

Gast
2008-09-26, 22:52:50
Und GERADE solche Methoden à la OnPaint zu überschreiben, geht gerne mal in die Hose.


Wer nicht mal so etwas triviales gemacht hat, der braucht sich gar nicht Entwickler nennen. Und was heißt in die Hose gehen? Bei dir liest man den typischen Dau Entwickler heraus. Was glaubst du überhaupt, warum die visuellen Komponenten so aussehen, wie sie aussehen? Genau, weil das irgendjemand implementiert hat. Und wenn man ein Custom Control schreibt und die UI frei erstellen will, dann überschreibt man die OnPaint. Oder willst du mir jetzt erzählen, dass du sowas noch nie gemacht hast?


Zum anderen sind grafische Oberflächen auch ein gewisser Sonderfall. Weder was ich da in Java noch in .NET gesehen habe, war von der Robustheit her so wirklich das Nonplusultra. Absolut nix im Vergleich zum restlichen Framework.
Das dort mit solchen halbseidenen Lösungen gearbeitet wird, ist eher Workaround denn Feature.


Das einzige, was hier nicht robust ist, das ist das Fehlen von Argumenten deinerseits.


Wenn du tatsächlich in die Verlegenheit geraten solltest, so tief in den Eingeweiden des Grafiksubsystems rumzuwühlen, musst du halt auch SEHR genau wissen was du tust.


Haha, in den Tiefen des Grafiksubsystems.... jetzt muss ich aber mal ganz laut lachen.


Kapselung dient dazu, die Oberfläche eines Objekts so weit es geht zu reduzieren - um eine robuste und verlässliche API zu garantieren.
Vererbung bricht eben diese Sicherheit ein gutes Stück weit auf. Eine Klasse so zu schreiben dass sie durch Vererbung nicht zerstört werden kann, ist ganz schön kompliziert.


Wenn man nicht will, dass die Klasse vererbt wird, dann versiegelt man sie. Die absolute Mehrheit der Frameworkklassen sind aber darauf ausgelegt, durch Vererbung erweitert zu werden. Jetzt gebe ich dir mal ein kleines praktisches Beispiel: Wenn ich z.B. ein Menü erstelle, dann reicht mir die Standardfunktionalität oft nicht aus. In der Regel lade ich meine UI über das Menü per Reflektion. D.h. ein Menüpunkt sollte also noch den full qualified namespace speichern, zusätzlich vielleicht noch andere Beschreibungstexte etc. Also schreibe ich eine neue Klasse, die eben von der Standardframework Klasse erbt (z.B. MenuStripItem, ContextMenuStripItem... was auch immer) und erweitere diese. Warum? Weil meine abgeleitete Klasse freilich trotzdem noch die Sichtbarkeit der Basisklasse hat und somit auch mit den Menü Controls (MenuStrip, ContextMenuStrip...) problemlos zusammenarbeitet.


Ein Interface hat aber wenigstens keine Nebenwirkungen auf bestehenden Code. Mit einer ungeschickt/bösartig vererbten Klasse kannst du bestehenden Code brechen. Das kannst du mit einem Interface nicht.


Ein Interface muss man selbst implementieren! Und je nach dem, wie du es implementierst, kann es vorhandenen Code brechen. Gerade mit einem Interface gibt man einen Freibrief, ungewollte Funktionalität einzubauen.



Wie Shink schon gesagt hat: lies dir mal ein paar aktuelle OOP Bücher durch. Ich zieh mir sowas nicht aus den Rippen.

Was Shink sagt, interessiert mich gar nicht. War das nicht der Zeitgenosse, der kürzlich hier in einem Thread als Java Profi nicht mal wusste, wie Pointer funktionieren?

Monger
2008-09-27, 01:11:55
Das einzige, was hier nicht robust ist, das ist das Fehlen von Argumenten deinerseits.


Der Gast Account ist kein Freibrief für Beleidigungen. Ich schlage vor, du überdenkst nochmal deinen Tonfall. Funktioniert ja im eingeloggten Zustand auch - mehr oder weniger.
Dankeschön.

PH4Real
2008-09-27, 11:00:21
Oh, Du hast das Buch wirklich gelesen:
"Item 16: Favor composition over inheritance" ;)

Ich glaube darauf spielt Monger an... habe jetzt keine Lust Beispiele dafür zu suchen, aber man kann den Satz mal in Google eingeben und findet viele Beispiele dafür (in C++ und Java).

Aber der wichtigste Grund: Vererbung kann, wenn "schlecht" angewandt, die Kapselung brechen. Vererbung ist dort unter Umständen ein zu "starker" Kontrakt auf Implementierungsebene.

Shink
2008-09-27, 16:18:49
Was Shink sagt, interessiert mich gar nicht. War das nicht der Zeitgenosse, der kürzlich hier in einem Thread als Java Profi nicht mal wusste, wie Pointer funktionieren?
Hmm... das interessiert mich jetzt aber doch wo ich das gesagt hab.

Shink
2008-09-27, 16:25:14
Ja klar, selten so einen Schwachsinn gelesen. Also anstatt z.B. die OnPaint Methode zu überschreiben und den übergebenen Parameter (Graphics object) zu verwenden, möchtest also lieber eine komlette Forms Klasse nachprogrammieren, über PInvoke die GDI+ verwenden und noch mal die komplette Message Loop reimplementieren, mit allen Ereignissen usw. Wahnsinns geniale Idee, Monger!
...oder z.B.:
Man implementiert das, was man zur Zeit in OnPaint macht, in einer Strategy-Klasse, die die GUI-Bibliothek selbstständig aufruft, da wir ihr unser GUI-Objekt gegeben haben, dass ein Interface implementiert in dem getPaintingStrategy() definiert ist.

Ich denke da gäbs noch einige Möglichkeiten

Gast
2008-09-27, 18:50:44
...oder z.B.:
Man implementiert das, was man zur Zeit in OnPaint macht, in einer Strategy-Klasse, die die GUI-Bibliothek selbstständig aufruft, da wir ihr unser GUI-Objekt gegeben haben, dass ein Interface implementiert in dem getPaintingStrategy() definiert ist.


Das ändert nichts daran, dass du entweder eine neue Klasse erstellen musst, die von der Basiskomponente erbt und dann z.B. die OnPaint überschreibst oder du hängst dich an das Ereignis. Letztes wiederum führt zu herrlichen Spaghetticode und widerspricht dem Sinn der Kapselung, wenn man das durch's ganze Projekt hinweg macht. Denn es bleibt ja dann i.d.R. nicht nur bei Grafik und man hat noch ne Menge mehr Anforderungen.

Shink
2008-09-27, 21:52:14
Das ändert nichts daran, dass du entweder eine neue Klasse erstellen musst, die von der Basiskomponente erbt und dann z.B. die OnPaint überschreibst
Nein, für diesen Ansatz reicht natürlich das Implementieren eines Interfaces. Da muss nichts überschrieben werden.

Übrigens: In SWT (eine der "aktuelleren" objektorientierten GUI-Libraries) sind alle wichtigen GUI-Klassen wie z.B. Table final (können also nicht abgeleitet werden). Scheint also irgendwie zu gehen.:rolleyes:

Gast
2008-09-28, 10:27:55
Nein, für diesen Ansatz reicht natürlich das Implementieren eines Interfaces. Da muss nichts überschrieben werden.


Dann musst du dich an Ereignissen hängen, wie ich oben geschrieben habe. Anders geht es nicht! Außer du würdest die Basiskomponente mit allen Interfaces von Grund auf reimplementieren, was natürlich dann mit eurem Wissen und der ganzen Diskussion hier nicht ganz zusammenpassen würde. Ansonsten bleiben dir nur noch zusammengesetzte Komponenten, die dann aber nicht mehr zu den Frameworkklassen kompatibel sind.

Shink
2008-09-28, 11:05:42
Ich denke es sollte klar sein dass das nur mit einem Framework geht dass darauf ausgelegt ist nicht über Subclassing verwendet zu werden.

Gast
2008-09-28, 11:48:00
Ich denke es sollte klar sein dass das nur mit einem Framework geht dass darauf ausgelegt ist nicht über Subclassing verwendet zu werden.

Das ist aber keine Kunst sich an Ereignisse zu hängen, das geht in jedem anständigen Framework seit eh und je. Die Kunst besteht darin ab einem gewissen Erweiterungsumfang/ Wiederverwendungsgrad die eigene Logik nicht ständig in Form von Eventhandlern etc. herumzuschleppen, sondern in die Komponente selbst zu integrieren. Und das geht ja logischerweise nur, wenn man von ihr erbt, um noch weiterhin kompatibel zu bleiben.

Ansonsten bleibt einem ja nur übrig, eine zusammengesetzte Komponente zu schreiben. Also eine Wrapperklasse, die die Basiskomponente enthält und sich da an bestimmte Ereignisse hängt, um diese zu manipulieren. Da verliert man aber einerseits jegliche Kompatibilität nach außen hin und darf andererseits jede Kleinigkeit nachimplementieren, wenn man Basisfunktionalität nach außen hin weitereichen will.

Monger
2008-09-28, 14:02:13
Ansonsten bleibt einem ja nur übrig, eine zusammengesetzte Komponente zu schreiben. Also eine Wrapperklasse, die die Basiskomponente enthält und sich da an bestimmte Ereignisse hängt, um diese zu manipulieren. Da verliert man aber einerseits jegliche Kompatibilität nach außen hin und darf andererseits jede Kleinigkeit nachimplementieren, wenn man Basisfunktionalität nach außen hin weitereichen will.
So bitter das klingt, aber das ist oftmals das kleinere Übel. Bei einer Vererbung ist die Gefahr sehr groß, dass du nicht dokumentierte Funktionen benutzst und veränderst - mit möglicherweise völlig unvorhersehbaren Folgen.

Nicht nur dass du keine Ahnung hast was deine Klasse eigentlich konkret tut - dieses Verhalten könnte sich mit jedem neuen Release des Frameworks ändern.
Mit Komposition hast du dieses Problem nicht, weil du dich - gezwungenermaßen - an die dokumentierte API hältst. Damit musst du zwar einen geeigneten Wrapper umsetzen - das ist aber wahrscheinlich deutlich robuster und immer noch weniger wartungsintensiv als wenn du blind von irgendeiner Klasse erben würdest.
Und ja: auch das habe ich in der Praxis schon leidvoll erfahren müssen.

Machen wir mal ein Beispiel:


public class Parent{

private List<Integer> blubb = new ArrayList<Integer>();

public void doSth(){
blubb.add(42);
}

}


Was die Klasse intern macht, sind halt Implementierungsdetails, und braucht dich eigentlich nicht zu interessieren. Wenn du jetzt "doSth()" überschreibst, geht diese Funktionalität verloren. Deshalb ruft man ja üblicherweise "super()" auf, damit man an der Stelle nix kaputt macht.
Somit ruft man aber auch gleichzeitig Code auf, von dem man keine Ahnung hat was er treibt. Das ist - so gesehen - eine undokumentierte API dieser Klasse.

Natürlich kann man eine Klasse so gestalten, dass man gefahrlos von ihr erben kann. Dafür hat man z.B. in Java die @Overridable Annotation eingeführt (allerdings viel zu spät), in .NET wird sie sogar erzwungen - sonst werden die Methoden nur überladen, und nicht etwa überschrieben. Außerdem müssen alle Nebeneffekte ausreichend dokumentiert sein.

Das ist aber halt leider das perfekte Gegenteil von dem was jahrelang propagiert wurde. Klassen sind naturgemäß eben erstmal NICHT zur Vererbung geeignet, sondern sie müssen ganz speziell auf dieses Ziel hin getrimmt werden.

Gast
2008-09-28, 15:56:33
Was die Klasse intern macht, sind halt Implementierungsdetails, und braucht dich eigentlich nicht zu interessieren. Wenn du jetzt "doSth()" überschreibst, geht diese Funktionalität verloren. Deshalb ruft man ja üblicherweise "super()" auf, damit man an der Stelle nix kaputt macht.
Somit ruft man aber auch gleichzeitig Code auf, von dem man keine Ahnung hat was er treibt. Das ist - so gesehen - eine undokumentierte API dieser Klasse.


Wenn du nicht möchtest, dass DoSomething überschrieben wird, dann machst du es folglicherweise auch nicht virtual/overridable. Selbst, wenn du den Member in der abgeleiteten Klasse ausblendest (z.B. durch new in C#), dann verwendet die Basisklasse immer noch ihren Basismember, sofern er nicht explizit überschrieben wird!!!


Natürlich kann man eine Klasse so gestalten, dass man gefahrlos von ihr erben kann. Dafür hat man z.B. in Java die @Overridable Annotation eingeführt (allerdings viel zu spät), in .NET wird sie sogar erzwungen - sonst werden die Methoden nur überladen, und nicht etwa überschrieben. Außerdem müssen alle Nebeneffekte ausreichend dokumentiert sein.


Keine Ahnung, was du hier meinst. Ich sehe kein Problem, wobei ich auch weniger für Java sprechen kann.


Das ist aber halt leider das perfekte Gegenteil von dem was jahrelang propagiert wurde. Klassen sind naturgemäß eben erstmal NICHT zur Vererbung geeignet, sondern sie müssen ganz speziell auf dieses Ziel hin getrimmt werden.

Diese allgemeine Aussage von dir ist natürlich mal wieder hochgradiger Unsinn!
Es kommt darauf an, was man genau mit abgeleiteten Klassen erreichen möchte. Um lediglich ein Subset mit einer Basisfunktionalität zu implementieren, sind alle Klassen geeignet. Vor allem weil alle abgeleiteten Klassen natürlich auch die aktuelle Funktionalität widerspiegeln, wenn die Basisklasse abgeändert wird. Wenn es darum geht eine flexibel erweiterbare Komponente zu erstellen, muss man sicher etwas mehr über das Design nachdenken.

Unter dem Aspekt "Vorsicht DAU" kann man jede Funktionalität kleinreden. Das ist für mich kein Argument, sondern eher ein Indiz für fehlendes Wissen/Können.

Shink
2008-09-28, 18:18:52
Unter dem Aspekt "Vorsicht DAU" kann man jede Funktionalität kleinreden. Das ist für mich kein Argument, sondern eher ein Indiz für fehlendes Wissen/Können.
Du redest wie einer dessen Libraries niemand ausser dir verwendet:biggrin:

Gast
2008-09-28, 19:15:17
Du redest wie einer dessen Libraries niemand ausser dir verwendet:biggrin:

Und du redest wie jemand, der keine Libraries entwickeln kann.

Vorsicht! Nicht persönlich werden!

Monger

Monger
2008-09-28, 20:52:04
Unter dem Aspekt "Vorsicht DAU" kann man jede Funktionalität kleinreden. Das ist für mich kein Argument, sondern eher ein Indiz für fehlendes Wissen/Können.
Wenn du es nicht wissen KANNST, ist das nunmal ein absolut entscheidendes Kriterium. Bei Bibliotheksfunktionen hat man nunmal in aller Regel keinen Einblick in die Implementierungsdetails einer Klasse.
Es kann also durchaus sein, dass die Oberklasse intern ein Verhalten hat, was die erbende Klasse bricht. Insbesondere wenn die Bibliothek sich ändert, kann man damit unter Umständen unbewusst den Client Code brechen. Das ist es völlig egal ob du DAU oder Vollblutprofi bist - wenn du unbewusst auf nicht dokumentiertem Verhalten aufsetzst, und jemand dieses Verhalten ändert (was er ja darf, schließlich ändert sich die API nicht), hast du ein Problem.

Edit:
Ich bringe mal das Beispiel aus "Effective Java" von Joshua Bloch, damit es nochmal klarer wird. Bezieht sich in diesem Fall nur auf Java, aber das Grundproblem so oder ähnlich natürlich in anderen Sprachen bestehen.

Nehmen wir mal an, wir wollten unsere eigene HashSet Implementierung schreiben:

public class InstrumentedHashSet<E> extends HashSet<E>{

private int addCount = 0;

public InstrumenteddHashSet(){}

public InstrumentedHashSet(int initCap, float LoadFactor){ super(initCap, loadFactor); }

@Override public boolean add(E e) {
addCount++;
return super.add(e);
}

@Override public boolean addAll(Collection<? extends E> c){
addCount += c.size();
return super.addAll(c);
}

public int getAddCount(){
return addCount;
}
}

Nehmen wir jetzt mal an, wir fügen über "addAll" drei Elemente hinzu. Was kommt bei getAddCount dann raus?
Eigentlich sollte drei rauskommen, tut es aber nicht. In HashSet setzt addAll implizit auf add auf. Da wir aber beide Methoden überschrieben haben, und keine Ahnung von der inneren impliziten Vereinbarung haben, wird unsere eigene add Methode dreimal aufgerufen - und unsere Zählung geht schief.
Aus Sicht des Bibliotheksentwicklers ist diese Form des Fehler kaum vorherzusehen, aus Sicht des Clients überhaupt nicht.

Gast
2008-09-29, 10:54:36
Spätestens beim Testen müsste dir der Fehler aufgefallen sein. Natürlich kann man hier die Schuld auf den Implementierer schieben, denn wenn addAll() intern noch add() aufruft, dann hätte man addAll() gar nicht überschreibbar machen dürfen. Oder man hätte es zumindest Dokumentieren müssen.

Aber das scheinst du ja auch immer gerne unter den Tisch fallen zu lassen. Damit nämlich etwas überschrieben werden kann, muss man es auch explizit als überschreibbar definieren. Wenn ich etwas als überschreibbar definiere, dann muss ich mir zwangsweise bewusst sein, dass meine Basisfunktionalität eventuell Code verwendet, auf den man keinen Einfluss hat. Andersrherum überschreibt man auch nicht einfach so irgendwelche Funktionalität, ohne darüber einen Gedanken zu verlieren.

Semantische Fehler kann man immer einbauen, wenn man will. Das ist kein Argument. Außerdem ging es hier ursprünglich um Vererbung, nicht um's Überschreiben. Du hättest genau so gut zwei neue Methoden einführen können anstatt die alten zu überschreiben und von denen dann jeweils die super.add() und super.addAll() aufrufen können. Genau so wenig hindert dich jemand daran, dich innerhalb einer abgeleiteten Klasse an Ereignissen von der vererbenden Klasse zu hängen, sofern diese welche anbietet.

Monger
2008-09-29, 11:33:03
Spätestens beim Testen müsste dir der Fehler aufgefallen sein.

Das meinst du jetzt nicht im Ernst, oder? ;)

Natürlich kann man hier die Schuld auf den Implementierer schieben, denn wenn addAll() intern noch add() aufruft, dann hätte man addAll() gar nicht überschreibbar machen dürfen. Oder man hätte es zumindest Dokumentieren müssen.

Ebend. Deshalb habe ich ja auch gesagt: Klassen müssen extra mit der Vererbung als Ziel entworfen und dokumentiert worden sein.
Implementierungsdetails zu dokumentieren widerspricht dem Konzept der Kapselung, und ist normalerweise ein No-Go für Klassenbibliotheken. Für Klassen die vererbt werden sollen, ist das aber notwendig.


Aber das scheinst du ja auch immer gerne unter den Tisch fallen zu lassen. Damit nämlich etwas überschrieben werden kann, muss man es auch explizit als überschreibbar definieren.

In Java nicht.


Semantische Fehler kann man immer einbauen, wenn man will.

Der Witz hier ist nunmal: es ist kein semantischer Fehler! Jede überschreibbare Funktion funktioniert für sich genau so wie sie soll. Diese Anomalie ist für den Client nichtmal vorhersehbar.

Ectoplasma
2008-09-29, 12:26:54
Ebend. Deshalb habe ich ja auch gesagt: Klassen müssen extra mit der Vererbung als Ziel entworfen und dokumentiert worden sein.


Wenn die Sprache das unterstützt, ist es eine gute Sache.


Implementierungsdetails zu dokumentieren widerspricht dem Konzept der Kapselung, und ist normalerweise ein No-Go für Klassenbibliotheken. Für Klassen die vererbt werden sollen, ist das aber notwendig.


Das Verhalten einer Klasse zu beschreiben; und damit sind alle öffentlichen Methoden gemeint, ist doch kein Implementierungsdetail. Anders ist es allerdings bei protected Methoden, da stimme ich dir zu.

Gast
2008-09-29, 12:29:39
In Java nicht.


Dann ist das aber ein eklatantes Problem von Java, nicht von OOP/Vererbung!

Monger
2008-09-29, 14:28:23
Das Verhalten einer Klasse zu beschreiben; und damit sind alle öffentlichen Methoden gemeint, ist doch kein Implementierungsdetail. Anders ist es allerdings bei protected Methoden, da stimme ich dir zu.
Wie in meinem Beispiel beschrieben, musst du aber tatsächlich Implementierungsdetails beschreiben, sogar in einer öffentlichen Methode, also: Methode x ruft intern Methode y auf, o.ä. .
Bei einer Sortier-Methode dokumentierst du ja normalerweise auch nicht die genaue Implementierung des Sortieralgorithmuses. Das ist schon ungewöhnlich.

Gast
2008-09-29, 15:51:22
Bei einer Sortier-Methode dokumentierst du ja normalerweise auch nicht die genaue Implementierung des Sortieralgorithmuses. Das ist schon ungewöhnlich.

Je nach Anforderungen dokumentiert man natürlich die Funktionen einer Methode. Und je nach Anforderung kann/muss man das sehr detailliert oder weniger detailliert machen.

In der offiziellen Java Dokumentation steht unter addAll():
http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractCollection.html#addAll(java.util.Collection)

"This implementation iterates over the specified collection, and adds each object returned by the iterator to this collection, in turn."

"Note that this implementation will throw an UnsupportedOperationException unless add is overridden (assuming the specified collection is non-empty)."

Das besagt ja nun eine klare Abhängigkeit von beiden Methoden. Es ist also nicht so wie du behauptest, dass der Konsument das gar nicht in Erfahrung bringen könnte.

Ectoplasma
2008-09-29, 16:11:47
Wie in meinem Beispiel beschrieben, musst du aber tatsächlich Implementierungsdetails beschreiben, sogar in einer öffentlichen Methode, also: Methode x ruft intern Methode y auf, o.ä. .
Bei einer Sortier-Methode dokumentierst du ja normalerweise auch nicht die genaue Implementierung des Sortieralgorithmuses. Das ist schon ungewöhnlich.

Ich stimme dir zu. Allerdings zielte meine Bemerkung eher darauf ab, dass man das Design einer Klasse überdenken sollte, falls zur Beschreibung des Verhaltens wirklich auch eine Beschreibung der Implementierung notwendig sein sollte.

Allgemein: Ich werde oft das Gefühl nicht los, dass man im OO Bereich vieles versucht in Regeln zu pressen. Das ist auch gut so, soweit es geht. Eine Klasse zu Designen ist aber schwieriger, als es sich anhört. Der Grund ist, dass es bei einer Aufgabe die es umzusetzten gilt, einen fachlichen Hintergrund gibt, der zunächst einmal gar nichts mit Softwareentwicklung zu tun hat. Genau hier liegt das Dilemma. Ich denke man könnte sich viele Diskussionen ersparen, wenn man sich zunächst einmal mehr Gedanken über das grundsätzliche Design einer umzusetzenden Aufgabe macht, als sich in technische Details zu verlieben.

Gast
2008-09-29, 21:41:42
Semantische Fehler kann man immer einbauen, wenn man will.
Diese Aussage disquallifiziert dich eigentlich, denn semantische Fehler macht keiner mehr heutzutage.

Gast
2008-09-30, 00:09:41
Diese Aussage disquallifiziert dich eigentlich, denn semantische Fehler macht keiner mehr heutzutage.

Semantik ist ein weitflächiges Gebiet in vielen Wissenschaften und beschränkt sich nicht alleinig darauf, ob die Syntax irgend einer Programmiersprache richtig zusammengesetzt ist. Es war natürlich von der Bedeutung des Programms die Rede.

Ectoplasma
2008-09-30, 01:01:30
Diese Aussage disquallifiziert dich eigentlich, denn semantische Fehler macht keiner mehr heutzutage.

Wie bitte? Semantische Fehler sind derzeit das größte Problem in der OO-Welt. Du leidest offensichtlich an selbstüberschätzung.

Gast
2008-09-30, 09:17:32
Semantik ist ein weitflächiges Gebiet in vielen Wissenschaften und beschränkt sich nicht alleinig darauf, ob die Syntax irgend einer Programmiersprache richtig zusammengesetzt ist. Es war natürlich von der Bedeutung des Programms die Rede.
Das nennt man dann Logik-Fehler
Wie bitte? Semantische Fehler sind derzeit das größte Problem in der OO-Welt. Du leidest offensichtlich an selbstüberschätzung.
Neee, semantische Fehler, kann der Compiler durchaus finden!

Gast
2008-09-30, 10:54:33
Das nennt man dann Logik-Fehler


Logik basiert auf klaren Aussagen. Hier geht es aber um die Interpretation/Bedeutung von Code, also Semantik.

Ectoplasma
2008-09-30, 11:12:00
Das nennt man dann Logik-Fehler

Neee, semantische Fehler, kann der Compiler durchaus finden!

Wie will der Compiler denn dieses Konstrukt finden:

class Integer {
...
};

class IntegerVector : public Integer {
...
};


Obiges Konstrukt habe ich wirklich schon gesehen. Syntaktisch völlig ok, semantisch komplett unbrauchbar.

Vielleicht reden wir aber auch nur aneinander vorbei.

Brillus
2008-10-01, 22:57:55
Hallo ihr 2 Streithähne bevor ihr euch hier gegenseitig umbringt, und weil mir gerade etwas langweilig ist möchte ich euch helfen hier mal mit einem Missverständniss aufräumen. Ihr verwendet ihr 2 Verschiedene definitionen von Semantischem Fehler.

Es gibt einmal den Allgemeinen:"Semantischer Fehler= Fehler in der Logic".

Und den für Compiler"Fehler in der semantischen Analyse(=Test gegen kontext Sensitive Grammatik)"

Beispiel für ersten sind einfach zu finden.

"Haus ist Hund."
oder als eine Funktion

int i;
void increassI(void)
{
i--;
}


Nach 2. definition ist der Code aber sehrwohl Semantisch korrekt.

Und dass, jetzt zu erklären muss ich etwas ausholen. Compiler kennen Grundsätzlich 3 Arten von Fehlern. Die lexikalischen, die Syntaktischen und die Semantischen.

Dabei tretten die Fehler immer in der entsprechenden Analyse auf.

lexikalische Fehler sind zum Beispiel

ü

Dazu ist noch zu sagen dies Bezieht sich auf C(++) und geht davon aus das es weder in einem String noch in einem Kommentar steht. Wenn ich mich richtig an die Java Regeln erinnere kann es in Java garkeine Lexikalsische Fehler geben.

10389hallo29929
zum Beispiel ist lexikalisch korrekt in c sowie in Java ist einfach Integerkonstante Bezeichner

Dies wäre aber natürlich ein Syntaktischer Fehler da man dass zu nichts reduzieren kann.

Folgender Code ist auch kein Syntaktischer Fehler, da er reduzierbar ist

void main(void)
{
int i;
j=0;
}

Beweis:

Void Identifier KlammerOffen Void KlammerZu GeKlammeroffen Int Identifier Semikolen Identifier Gleich InterKonstante Semikolen GeKlammerZU
*->
Funktionskopf GeKlammeroffen Dekaration Zuweisung GeKlammerZU
*->
Funktionskopf Funktionsrumpf
->
Funktion
->
Programm

Abe wie ihr sicher erratten könnt schlägt da die Semantische Analyse zu und sagt das eine nicht deklarierte Variable genutzt wir.

@Edit Fehler bereinigt thx@xmas

Xmas
2008-10-03, 13:28:24
10389hallo29929
zum Beispiel ist lexikalisch korrekt in c sowie in Java ist einfach Integerkonstante Bezeichner Integerkonstante
Das wäre lediglich "Integerkonstante Bezeichner".

Ectoplasma
2008-10-03, 15:22:39
Hallo Brillus,

danke für deine Erklärungen, diese sind mir noch aus dem Studium bekannt. Eine kleine Auffrischung ist aber immer gut.

Ich meinte eher die OO Modellierung und deren semantische Analyse. Bei der OO Modellierung geht es um weltliche Dinge. Vererbung ist ein Hilfswerkzeug, um bestimmte Zusammenhänge eines konkreten Problems einfacher bzw. verständlicher abbilden zu können. Man muß sie nicht verwenden, aber man kann es.

crazyRaccoon
2008-10-05, 02:46:22
Hier wird teilweise ganz schön harsch eine Meinung angegriffen (Mongers), die eigentlich -wie ich dachte- doch weit verbreitet und akzeptiert ist. Er sagt ja ausdrücklich NICHT dass Vererbung völlig nutzlos sei sondern weist lediglich daraufhin, dass man damit vorsichtig umgehen sollte.

Mich wundert, dass hier das typische "Ein Quadrat ist ein Rechteck" noch nicht genannt wurde:


class Rechteck
{
private int m_a, m_b;

public Rechteck(int a, b) :m_a(a), m_b(b) {}

public stretch(int x, int y)
{
m_a+=x;
m_b+=y;
}
};

class Quadrat : Rechteck
{
public Quadrat (int a) :Rechteck(a, a) {}
};

...
Quadrat quadrat = new Quadrat(5);
quadrat.stretch(1,2); //???

Man stelle sich das Ganze mit zwei semantisch komplexeren Objekten vor, wo man nicht mehr so leicht nachvollziehen kann, was eine bestimmte Methode intern tut.

Es geht um contracts. Sicher, laut Vererbung ist eine Unterklasse immer gleichwertig anstelle einer Oberklasse einsetzbar, aber ist sie das wirklich? Sind wirklich alle Vor- und Nachbedingen einer Oberklasse auch in der Unterklasse erfüllt?

Ich denke das Problem ist, dass Vererbung in den meisten OO-Einführungen meistens gleich an erster Stelle als DAS OO-Feature und dann dazu noch so locker flockig erklärt wird, ohne auf die Probleme einzugehen, die damit verbunden sind.

Bietchiebatchie
2008-10-05, 08:28:12
Hier wird teilweise ganz schön harsch eine Meinung angegriffen (Mongers), die eigentlich -wie ich dachte- doch weit verbreitet und akzeptiert ist. Er sagt ja ausdrücklich NICHT dass Vererbung völlig nutzlos sei sondern weist lediglich daraufhin, dass man damit vorsichtig umgehen sollte.
Ich denke mal, das kommt daher, dass die meisten pro-Vererbung-posts von gästen kamen - nicht jeder will sich mit leuten rumärgern, die zu faul sind sich einen Namen zu überlegen. Ansonsten vermute ich, dass die meisten werden Monger im großen und ganzen zustimmen.


Mich wundert, dass hier das typische "Ein Quadrat ist ein Rechteck" noch nicht genannt wurde (...)

Ein paar Gedanken meinerseits dazu:
"Ein Quadrat ist ein Rechteck" ist eine mathematische Trivialität. Daher sollte diese in einem Softwaredesing auch berücksichtigt werden.
Eine ähnliche Trivialität: Ein Quadrat ist einem anderen gleich ist wenn es in allen vier Eckpunkten übereinstimmt - insbesondere ist es von diesem in keinster Weise zu unterscheiden (im Gegensatz zu einer graphischen Repräsentation eines Rechtecks - diese kann ja z.B. beliebig gefärbt werden und damit sind auch "gleiche" graphische Rechtecke voneinander unterscheidbar).
Daraus folgert das ein Rechteck keine Identität hat - es ist rein durch seine Merkmale definiert (in dem Fall die vier Eckpunkte). Wenn ein Objekt allerdings keine Identität hat, folgt daraus direkt (rein logisch), dass es unveränderlich ("immutable") ist.
Auf das Rechteck bezogen heißt das: Wenn das Design an mathematischen Gegebenheiten orientieren soll, darf die Rechteck-Klasse keinerlei Methoden haben, die das Objekt verändern.
Konkreter:


class Rechteck
{
private int m_a, m_b;

public Rechteck(int a, b) :m_a(a), m_b(b) {}

public Rechteck stretch(int x, int y)
{
return new Rechteck(a + x, b + y);
}
};


Edit (vergessen): Was hat das ganze mit Vererbung zu tun? Ganz einfach, wenn das Rechteck nur Methoden hat, die den internen Zustand nicht verändern, ist in jedem Fall eine Instanz von einem Quadrat auch ein gültiges Rechteck.

Gast
2008-10-05, 14:46:58
Hier wird teilweise ganz schön harsch eine Meinung angegriffen (Mongers), die eigentlich -wie ich dachte- doch weit verbreitet und akzeptiert ist. Er sagt ja ausdrücklich NICHT dass Vererbung völlig nutzlos sei sondern weist lediglich daraufhin, dass man damit vorsichtig umgehen sollte.


Das hat er dann im Nachhinein etwas relativiert. Die Ausgangsmeinung war - und damit hat der die von Shink vertreten - Vererbung wäre etwas schlechtes, was man umgehen sollte. Und es ist gerade diese undifferenzierte, dogmatische Sichtweise, die eine heftige Gegenreaktion mit sich bringt. Und ich glaube nicht, dass die meisten Leute da auch nur ansatzweise im großen und ganzen seiner Meinung sind.

Monger
2008-10-05, 14:52:10
Ich zitiere mich an der Stelle mal selbst:
Shink hat zumindest damit Recht, dass die Bedeutung von Vererbung für objektorientierte Sprachen gerne überschätzt wird.
Vererbung kann böse Nebenwirkungen haben wenn erbende und vererbende Klasse nicht durch den selben Entwickler gepflegt werden.

Vererbung ist definitiv NICHT das wichtigste Feature von OOP. Kapselung ist vermutlich das wichtigste, dicht gefolgt von Abstraktion über Interfaces.
Das war mein erster Beitrag zu dem Thema, und den habe ích mitnichten nachträglich relativiert.

Brillus
2008-10-05, 15:26:41
Das wäre lediglich "Integerkonstante Bezeichner".
Jo danke hast recht korrigiers gleich

Gast
2008-10-05, 15:38:19
Ich zitiere mich an der Stelle mal selbst:

Das war mein erster Beitrag zu dem Thema, und den habe ích mitnichten nachträglich relativiert.

Das ist ja letzendlich dasselbe und ist genau solcher Unsinn.

Shink
2008-10-05, 18:24:56
Die Ausgangsmeinung war - und damit hat der die von Shink vertreten - Vererbung wäre etwas schlechtes, was man umgehen sollte. Und es ist gerade diese undifferenzierte, dogmatische Sichtweise, die eine heftige Gegenreaktion mit sich bringt. Und ich glaube nicht, dass die meisten Leute da auch nur ansatzweise im großen und ganzen seiner Meinung sind.
Ich schon. Klassenvererbung ist eine tolle Sache um Funktionalität in bestehende Klassen reinzuhacken (überschreiben wir mal "toString()" o.ä.) oder eine an die Realität angelehnte Klassenstruktur zu haben (Quadrat ist ein Rechteck...). Im Design großer Softwaresysteme ist sie eher im Weg, da ist man mit Interface-Vererbung oder Aggregation (Quadrat hat ein Rechteck) eigentlich immer flexibler dran.

Nun ja, dann bin ich mal gespannt auf die weiteren nicht-Gast-Kommentare die auch nach genauerem darüber Nachdenken nicht ansatzweise dieser Meinung sind.

Ganon
2008-10-05, 20:16:49
Das ganze Thema erinnert mich an das witzige Kapitel aus "Entwurfsmuster von Kopf bis Fuß" :D Wo bei dem Programm SimEnte die Gummiente über den Bildschirm geflogen ist, weil man zu extrem vererbt hat ;D

crazyRaccoon
2008-10-06, 00:07:53
...darf die Rechteck-Klasse keinerlei Methoden haben, die das Objekt verändern.
[...]
wenn das Rechteck nur Methoden hat, die den internen Zustand nicht verändern, ist in jedem Fall eine Instanz von einem Quadrat auch ein gültiges Rechteck.
Die Forderung ist richtig und bei so einem trivialen Beispiel mit dem Quadrat würde wohl kaum jemand den aufgezeigten Fehler machen; das Beispiel sollte natürlich nur als Aufhänger dienen.

Bei komplexeren Klassen kann es schwerer übersehbar sein.

Für sich gesehen ist das Design einer Klasse Rechteck mit der Methode stretch vollkommen in Ordnung; angenommen man hat sich erst später dazu entschlossen, eine Unterklasse Quadrat abzuleiten, kommen aber plötzlich die Probleme. Vererbung ist dann in solchen Fällen eher kontraproduktiv. Man glaubt man hat mit Vererbung für etwas eine flexible Lösung gefunden und sieht sich dann mit unvorhergesehenen Problemen konfrontiert. Dann ist sie plötzlich nicht mehr so flexibel wie sie sich anhört. In schlimmsten Fall kann es dazu führen, dazu man eine starke Kopplung zwischen Ober- und Unterklasse erwirkt, die man mit Vererbung gerade zu vermeiden dachte.