Momentan unternehme ich erste Programmierschritte in Python und denke,
die eigentlichen Grundlagen verstanden zu haben, einschließlich OOP,
Klassen und Methoden.
Was ich aber überhaupt nicht verstehe: Woher weiß ich wie ich meinen
Code in welche Klassen und Methoden zu unterteilen habe?
(ich möchte in Kodi ein Video-Addon programmieren, das vom Umfang her
kaum mehr als Skripte sind, ausgedruckt weniger als ein Dutzend DIN
A4-Seiten. Bis jetzt ist das alles nur mit Funktionen programmiert, das
funktioniert, scheint aber unschön zu sein. Als Vorlage dient
https://github.com/romanvm/plugin.video.example).
Tim schrieb:> Was ich aber überhaupt nicht verstehe: Woher weiß ich wie ich meinen> Code in welche Klassen und Methoden zu unterteilen habe?
Erfahrung.
Prinzipiell sollen die "tiefsten" Klassen und Methoden möglichst
unspezifisch sein. Mit jeder weiteren Schicht wirds dann spezifischer.
Auch sollte die sich das, was eine Methode tut mit einem möglichst
passenden Verb beschreiben lassen - welches dann auch für den
Methodennamen verwendet werden soll.
Also statt
1
doWork()
lieber
1
read()
2
parse()
3
sort()
4
write()
Hintergründe sind einerseits, dass man unspezifischere Klassen &
Methoden evtl. für was anderes wiederverwenden kann.
Und es vereinfacht die Fehlersuche. Wenn z.B. bei obigen Beispiel die
Werte nicht richtig sortiert sind, sucht man in der "sort" Methode.
Wahrscheinlich bequemer als 3/4 der "doWork" Methode durchzuarbeiten.
BTW: Buchempfehlung: Weniger schlecht programmieren - von Katarina
Passig u.A.
Tim schrieb:> Bis jetzt ist das alles nur mit Funktionen programmiert, das> funktioniert, scheint aber unschön zu sein.
Also hast du funktionierenden Code, den du jetzt schöner machen willst?
Das ist doch eine dankbare Ausgangsituation zum lernen.
Hast du mehrere nahezu identische Codeabschnitte? -> In eine Methode
packen, die kleinen Unterschiede über Parameter handeln.
Tim schrieb:> Woher weiß ich wie ich meinen> Code in welche Klassen und Methoden zu unterteilen habe?
Wie Matthias sagt, Erfahrung. Es gibt nicht die eine Schritt-für-Schritt
Anleitung. Das Ziel sollte eben sein, dass du den Überblick nicht
verlierst und auch in ein paar Monaten wieder verstehst, wie dein Code
funktioniert.
Das heißt nicht unbedingt, dass man sich auf Zwang irgendwelche Klassen
aus den Fingern saugen muss.
Eine Klasse die nichts repräsentiert ist das Äquivalent zur
Krimskrams-Schublade im echten Leben. Sieht aus als hätte man
aufgeräumt, macht Dinge wiederzufinden aber nicht einfacher.
Tim schrieb:> Was ich aber überhaupt nicht verstehe: Woher weiß ich wie ich meinen> Code in welche Klassen und Methoden zu unterteilen habe?
3 Antworten dazu:
1. Python selbst stellt hier überhaupt keine Anforderungen. Das gehört
zu den schönen Eigenschaften dieser Sprache: Ob du Klassen (oder auch
überhaupt Funktionen) bildest, bleibt dir grundsätzlich selbst
überlassen.
2. Sofern man auf Bibliotheken oder allgemein bereits vorhandenen Code
aufbaut, könnte man gezwungen sein, mit Klassen zu hantieren, z.B. von
einer bereits existierenden Klasse zu erben und dann bestimmte Methoden
zu überschreiben. Ein einigermaßen einfaches Beispiel findest du bei
exceptions: Wenn du eine eigene exception haben willst, musst du selbst
etwas von der Klasse Exception (oder BaseException, aber meistens die
falsche Wahl ...) ableiten.
3. Dann bleibt noch die Situation, dass du selbst objektorientiert
schreiben willst. Dazu schaust du dir am besten mal Bücher zum Thema
objektorientierte Analyse und Design an, außerdem viel möglichst realen
Code. Ein gutes Gespür, wie fein oder grob Klassen gebildet werden
sollten, entwickelst du nur mit Erfahrung.
Tim schrieb:> Was ich aber überhaupt nicht verstehe: Woher weiß ich wie ich meinen> Code in welche Klassen und Methoden zu unterteilen habe?
Wie schon jemand schrieb, Erfahrung. Aber nehmen wir mal die ganz alte
Schule:
Klassen sind Baupläne für Objekte.
Objekte sind Instanzen einer Klasse.
Objekte repräsentieren (virtuell) reale oder abstrakte Dinge.
An dem Punkt kommen dann so typische Beispiele für Objekte wie Auto,
Reifen, Rechteck, Linie, Server, Geldautomat. Praktisch heißt das, du
siehst dir dein Problem an und arbeitest heraus welche Dinge bei deinem
Problem eine Rolle spielen. Danach bildest du die Klassen.
Objekte kann man bitten für das Objekt typische Dinge zu tun oder Fragen
über sich zu beantworten. Beispiel: Auto->anfahren(), Auto->bremsen(),
Auto->welcheFarbeHastDu().
Was ein Objekt tun kann und auf welche Fragen es antworten kann wird
durch Methoden beschrieben.
Also bekommen deine Klassen (die Baupläne für Objekte sind), jeweils die
Methoden die für die jeweiligen Objekte typisch sind und die für die
konkrete Problemlösung nötig sind.
Das "für das Objekt typisch" ist dabei wichtig. Viele OO-Programme
halten sich nicht dran und es kommt Müll raus bei dem Klassen/Objekte
nur zufällige Gruppierungen von Methoden sind.
Ebenso ist "für die konkrete Problemlösung" wichtig. Beispiel: Wenn dein
virtuelles Auto nur fahren und anhalten können muss wirst du, weil es
einfacher ist, keine Methoden zum Blinken oder zum Betätigen der Fenster
schreiben.
Es geht, bis auf Ausnahmen, nicht darum ein reales oder abstraktes Ding
vollständig im Rechner abzubilden. Ausnahmen siehst du bei Bibliotheken
und Frameworks. Die müssen mehr anbieten als in einer einzelnen
Anwendung benötigt wird um möglichst viele Anwendungen zu unterstützen.
Tim schrieb:> Was ich aber überhaupt nicht verstehe: Woher weiß ich wie ich meinen> Code in welche Klassen und Methoden zu unterteilen habe?
Für einfache Projekte kann es helfen, als Text auszuformulieren was man
möchte:
Ich habe ein Auto. Ein Auto ist ein Sonderfall eines Kraftfahrzeuges.
Ein Kraftfahrzeuges hat eine Motorleistung und kann fahren (vorwärts und
rückwärts), lenken (rechts und links) und bremsen. Um fahren zu können,
braucht es mindestens zwei Achsen mit jeweils mindestens einem Rad,
mindestens eine Achse lenkbar. Ein Auto hat zwei Achsen mit zwei Rädern,
eine davon lenkbar. Im Gegensatz zum generischen Kraftfahrzeug hat es
auch eine Karosserie, mit Lack in einer bestimmten Farbe.
Kraftfahrzeug:
- Eigenschaften: Leistung
- Member: Achsen[]
- Methoden: fahre(richtung), lenke(richtung), bremse()
Achse:
- Member: Räder
LenkbareAchse (erbt von Achse):
- Eigenschaften: Ausrichtung
- Member: Räder[]
- Methoden: lenke(richtung)
Auto (erbt von Kraftfahrzeug, implementiert Karosserie):
- Eigenschaften: Leistung, Farbe
- Member: Achsen[2] = { Lenkbare Achse(), Achse() }, Karosserie
- Methoden: fahre(richtung), lenke(richtung), bremse()
Wüstenbuggy (erbt von Kraftfahrzeug):
- Eigenschaften: Leistung
- Member: Achsen[2] = { Lenkbare Achse(), Achse() }
- Methoden: fahre(richtung), lenke(richtung), bremse()
Radlader (erbt von Kraftfahrzeug, implementiert Karosserie):
- Eigenschaften: Leistung, Farbe
- Member: Achsen[2] = { LenkbareAchse(), LenkbareAchse() }, Schaufel
- Methoden: fahre(richtung), lenke(richtung), bremse(),
hebeSchaufel(richtung)
Karosserie:
- Eigenschaften: Farbe
--
Praktikabilität dieser Methode hängt vom Anwendungsfall ab.
Das kommt durch Erfahrung und jahrelanges Programmieren.
Erst dann kann man Abschätzen, ob die Klassenstruktur hilfreich ist oder
mehr Probleme bei Änderungen macht.
Dazu gehört dann auch die Reflektion: "Dieses Konstrukt, was ich vor 5
Jahren gemacht habe, war vollkommen überdimensioniert".
Bei nächsten Mal macht man es einfacher.
Ich habe z.B. bei Kollegen eine Vererbungsstruktur mit 5 Ebenen gesehen.
Da blickt heute keiner mehr durch.
Aber das muss man erst einmal selber erleben.
Danke für die bisherigen Kommentare.
Welches Buch/Tutorial mit praxisnahen Beispielen käme noch infrage?
Geplante Anwendung --> umgesetzte Klassenstruktur
Wenn ich das richtig gelesen habe, willst du ja "nur" ein Kodi-PlugIn
programmieren, bist also nicht im professionellen Umfeld unterwegs?
Dann mach dir nicht zu viele Gedanken, leg einfach mal los. Wenn du
klein anfängst und dein Projekt nach und nach wächst, wird das auch mit
der Klassenstruktur. Du könntest ggf. auch verraten, was du vorhast?
Auch
Matthias S. schrieb:> Hintergründe sind einerseits, dass man unspezifischere Klassen &> Methoden evtl. für was anderes wiederverwenden kann.
kannst du nach hinten anstellen.
Wenn du etwas aus einem Hobbyprojekt für ein anderes Projekt verwenden
kannst gibt es folgende Möglichkeiten:
* du erinnerst dich nicht mehr daran, es schon gemacht zu haben und
machst es neu
* du musst es sowieso anpassen, also reicht blödes Copy&Paste
* jemand anderes hat es schon besser gemacht und auf Git od. ähnl.
veröffentlicht
Solltest du dich im prof. Umfeld bewegen, ist man wahrscheinlich sowieso
gut beraten, sich dem existierenden aktuellen Umfeld anzupassen.
🐧 DPA 🐧 schrieb:> vn nn schrieb:>> Für einfache Projekte kann es helfen, als Text auszuformulieren was man>> möchte:>> Ich will Kreise und Ellipsen zeichnen. Ein Kreis is ein sonderfall einer> Ellipse. Moment mal, oh, shit:> https://de.wikipedia.org/wiki/Kreis-Ellipse-Problem
Der TO steigt gerade in die OOP ein und du kommst
hier mit einem der kompliziertesten Probleme der
OOP daher, ganz großes Kino.
@TO Einfach mal anfangen, aus Fehlern lernt man.
merciless
Die Idee hinter OOP ist ja schön, es erscheint Intuitiv, Dinge zu
Klassifizieren, und sich zu überlegen x ist ein y, y ist ein z, etc.
Aber Ich denke es ist wichtig von Anfang an zu verstehen, das sich eben
nicht alles sauber auf die Weise Abbilden lässt.
In punkto Programmstrukturen baut OOP auf 3 Dinge auf:
1) Das Kapseln/Zusammenfassen zusammengehörender Daten (Objekte)
2) Das assoziieren auf diese anwendbarer Funktionen zu diesen
(Methoden)
3) Erweitern bestehender Objekte, sowie das Überschreiben derer
Methoden und Eigenschaften. (Vererbung)
1 Würde ich zumindest bei so ziemlich allen nicht rein funktionalen
Sprachen als essenziell betrachten.
2 Kann vor allem zusammen mit 3 sehr nützlich sein.
Aber 3 kann halt auch manchmal mehr Probleme bereiten, als es löst.
Der Zweck von dem ganzen OOP zeug ist ja eigentlich, auf intuitivem Weg
Code dublizierung zu vermeiden, und die Codequalität und Zuverlässigkeit
des Programms zu erhöhen. Das kann man damit oft auch recht gut machen.
Das blöde daran ist halt nur, wenn man nicht aufpasst und es übertreibt,
kann man sich schnell in eine Ecke designen, und grobe Designprobleme
sind dann oft viel schwieriger zu lösen, als ein paar flache Code Bugs /
Fehlfunktionen. Und nur um Code zusammenzufassen und übersichtlich zu
halten, braucht man nicht unbedingt das ganze OOP und Vererbung.
(Stichwort gemeinsamen Code in Unterfunktionen auslagern.)
Zudem bieten Sprachen heute noch unzählige andere Sprachkonstrukte, die
auch ergänzend wirken können, und oft der stumpfen Vererbung vorzuziehen
sind. Außerdem haben viele gute Programmierparadigmen und Strukturen,
die mit OOP in Verbindung gebracht werden, damit eigentlich gar nichts
zutun und können auch ohne verwendet werden.
Da ich schon mal dabei bin, will ich gleich mal Werbung für
(Objekt-)Interfaces machen. Damit kann man angeben, man ist Interessiert
an Daten/Objekten, mit denen man gewisse Dinge machen kann, und man kann
angeben, bei welchen Arten von Daten/Objekten das gemacht werden kann.
(Implementieren muss man es jeweils natürlich trotzdem noch). Oft
braucht man dann Vererbung nicht mehr, und ist viel flexibler, weil man
sich nicht mehr überlegen muss, was ist was, sondern einfach sagen kann,
was kann momentan was.
Die meisten restlichen Vererbungs use-cases kann man auch loswerden,
indem man eine ist-ein Beziehung durch eine hat-ein Beziehung ersetzt.
Dass kann dann auch Probleme lösen, z.B. wenn unklar ist, ob X wirklich
Y ist, wenn unabhängige Objekteigenschaften kollidieren, und vieles
mehr.
Am Ende bleiben eigentlich nicht mehr viele Fälle übrig, in denen
Vererbung tatsächlich die beste Lösung ist. Unglücklicherweise ist es in
OOP Kreisen aber üblich, alles ins OOP Korsett zu zwängen, was anderes
wäre ja falsch und schlechter Stil. Am Ende muss man sich meistens dann
halt dem anpassen, was im jeweiligen dem Gebiet üblich ist.
Es gibt immer viele gute Lösungen, und meistens nicht nur den einen
richtigen Weg. Man muss nur schauen, dass was man verzapft möglichst
einfach Verständlich sowie von anderen erweiterbar/wartbar ist, und dass
es auch zuverlässig Funktioniert. In der Regel ist am Ende weniger oft
mehr.
... und wenn man nicht aus allem einen Glaubenskrieg und eine
philosophische Dissertation machen muss, ist OOP einfach nur ein
praktisches Werkzeug, das bei korrekter Anwendung die gewünschten
Ergebnisse liefert.
Das Kreis-Ellipse-Problem basiert übrigens zu mindestens 90 % auf der
übertrieben vereinfachten Vorstellung, dass Vererbung mit einer "ist
ein"-Beziehung in natürlicher Sprache gleichzusetzen ist. Sobald man
das verstanden hat, ist es kein "Problem" mehr, eher ein lehrreiches
Beispiel, warum man sich bei der Modellierung weniger auf anschauliche
Vorstellungen als auf die tatsächliche Schnittstelle, die eine Klasse
definiert, konzentrieren sollte.
Tim schrieb:> Welches Buch/Tutorial mit praxisnahen Beispielen käme noch infrage?> Geplante Anwendung --> umgesetzte Klassenstruktur
Eins vorweg:
Erst einmal solltest du dir dessen bewusst sein, dass Objektorientierung
in Python kein Dogma ist.
Python unterstützt neben der objektorientierten auch die prozedurale und
ein Bisschen die funktionale Programmierung. Guten Python-Code zeichnet
aus, dass er jedes der genannten Paradigmen dort und nur dort einsetzt,
wo es auch logisch sinnvoll ist.
Anders als bspw. in Java ist es in Python absolut verpönt, jedes noch so
kleine Detail in eine eigene Klasse zu wickeln, nur um irgendwelchen
OOP-Grundsätzen Genüge zu tun.
Es gibt viele guten Python-Programme, die ohne oder mit nur ganz wenigen
selbstgeschriebenen Klassen auskommen.
Andererseits ist es natürlich unklug, dort, wo die die OOP sinnvoll
einsetzbar ist, auf ihre Möglichkeiten und Vorteile zu verzichten.
Ich habe zwar noch nie ein Python-Buch gelesen, aber nach kurzem
Überfliegen erscheint mir das folgende ganz passend zu sein:
"Python 3 Object-Oriented Programming" von Dusty Phillips
Du kannst ja auch mal einen Blick hineinschmeißen, um zu sehen, ob es
dir zusagt.
Zum Schluss noch ein kurzer Tipp, wie du für den Anfang ganz leicht
potentielle Objekte in deinem Programm finden kannst:
Wenn du das Programm logisch in mehrere Funktionen aufgeteilt hast und
dann feststellst, dass du häufig das Schlüsselwort "global" benutzen
musstest, oder dass mehrere der Funktionen gemeinsame Argumente haben,
ist das ein starkes Indiz dafür, dass die globalen Variablen (oder ein
Teil davon) bzw. die gemeinsam genutzten Funktionsargumente, jeweils
zusammen mit den Funktionen, die sie verwenden, ein Objekt bilden.
Das ist natürlich nur ein ganz kleiner Aspekt der OOP. Schwieriger wird
es u.a. bei der Frage nach einer optimalen Vererbungshierarchie bzw.
wann Vererbung überhaupt sinnvoll ist.
Der Beginn liegt bei den Fundamenten der OOP. Dort wo Daten und Code
zusammengehoeren, kann man die mit Klassen verbinden.
Das ist schon sehr effizient.
Vererbung kommt sehr viel speaeter, wenn in einem Projekt auffaellt,
dass man aehnliche Klassen bildet.
🐧 DPA 🐧 schrieb:> Die Idee hinter OOP ist ja schön, es erscheint Intuitiv, Dinge zu> Klassifizieren, und sich zu überlegen x ist ein y, y ist ein z, etc.> Aber Ich denke es ist wichtig von Anfang an zu verstehen, das sich eben> nicht alles sauber auf die Weise Abbilden lässt.>> In punkto Programmstrukturen baut OOP auf 3 Dinge auf:> 1) Das Kapseln/Zusammenfassen zusammengehörender Daten (Objekte)> 2) Das assoziieren auf diese anwendbarer Funktionen zu diesen> (Methoden)> 3) Erweitern bestehender Objekte, sowie das Überschreiben derer> Methoden und Eigenschaften. (Vererbung)
Dr. Alan Kay, der die Objektorientierung und das Fachwort dafür erfunden
hat, sagt dazu das Folgende: "OOP to me means only messaging, local
retention and protection and hiding of state-process, and extreme
late-binding of all things." [1]
Zusammengehörige Daten kann ich auch ohne OOP zusammenfassen, dazu
bieten klassische Programmiersprachen wie C Strukturen (struct) und
Pascal seine Records. Aber diese Sprachen bieten sprachseitig keine
Möglichkeit, die Funktionen, die sie verarbeiten sollen, an ihre
Datenstrukturen zu binden. Mit ein bisschen Gehacke kann man etwas
Ähnliches auch in vielen nicht-OO-Sprachen wie C abbilden, aber nur sehr
unelegant; IMHO gehören Deine ersten beiden Punkte jedenfalls direkt und
sehr eng zusammen.
Die Vererbung ist eine andere Geschichte und eine Folge aus dem zuvor
Gesagten, und für die Vererbung ist es teilweise richtig zu sagen, daß
sie der Deduplizierung von Code dienen. Im Grunde dient die Vererbung
aber zunächst dem Ziel, ähnliche Objekte zusammenzufassen und sie
typsicher zu machen. In dem beliebten Auto-Beispiel heißt das, daß ich
eine Klasse Fahrzeug mit einer abstrakten Methode beschleunige() habe,
und davon dann meine Klassen Auto und Motorrad ableite. Wenn nun die
Businessjungs sagen, "beschleunige das Ding", dann kann ich einfach die
Methode beschleunige() autrufen, und zwar unabhängig davon, ob das
betreffende Dingsi jetzt ein Auto oder ein Motorrad ist.
> Der Zweck von dem ganzen OOP zeug ist ja eigentlich, auf intuitivem Weg> Code dublizierung zu vermeiden, und die Codequalität und Zuverlässigkeit> des Programms zu erhöhen. Das kann man damit oft auch recht gut machen.
Der tiefere Sinn der OOP ist IMHO, den Code übersichtlicher zu
organisieren und die Daten sowie die Funktionen, die sie bearbeiten,
zusammenzufassen. Damit kann man auch die Duplizierung von Code
vermeiden, aber das ist nicht das Ziel.
> Das blöde daran ist halt nur, wenn man nicht aufpasst und es übertreibt,> kann man sich schnell in eine Ecke designen, und grobe Designprobleme> sind dann oft viel schwieriger zu lösen, als ein paar flache Code Bugs /> Fehlfunktionen.
Ich weiß, diese Kritik an der OOP ist nicht neu, und war angesichts
jener Zeit vor zwanzig Jahren, als "Objektorientierung" ein
Verkaufsargument für manche Scharlatane war und es auch noch nicht so
sehr viel Erfahrung mit dem Konzept gab, durchaus auch berechtigt. Aber
es erscheint mir etwas widersinnig, jemandem ein neues Paradigma
nahezubringen, indem man vor historischen (und teilweise hysterischen"
Irrtümern warnt. Fakt ist: die saubere Strukturierung von Code und Daten
ist eine Sache der Erfahrung, der Praxis, abstraktem Denkvermögen und
der Fähigkeit, sein Problem vor und während der Umsetzung immer wieder
aus der Vogelperspektive zu betrachten. Das gilt allerdings nicht nur
für OOP, sondern auch für imperative, prozedurale, und funktionale
Programmierung.
> Und nur um Code zusammenzufassen und übersichtlich zu> halten, braucht man nicht unbedingt das ganze OOP und Vererbung.
Das ist ein bisschen... halbrichtig. Um die OOP sinn- und nutzbringend
einsetzen zu können, muß man sie idealerweise komplett kennen. Richtig
ist allerdings, daß man nicht in jedem OO-Programm alle Mittel und
Möglichkeiten nutzen muß, die sie einem bietet. Ich persönlich schätze,
daß etwa 70% meiner Python-Programme eigene Klassen nutzen, aber maximal
5% meiner Python-Programme nutzen zB. die Vererbung. Es ist gut seinen
ganzen Werkzeugkasten zu kennen, aber trotzdem nur den sinnvollen Teil
davon zu benutzen -- nur weil ich einen Hammer darin habe, muß ich ihn
nicht benutzen, um eine Schraube in die Wand zu bekommen.
[1] http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en
Ich denke, danach hast du eine recht brauchbare Vorstellung davon,
welche Vorteile Klassen usw. bieten.
Auch über die klasischen Beispiele, die Gegenstände im Code nachbilden
wollen, hinaus.
Das hier liest du dir durch:
http://openbook.rheinwerk-verlag.de/oop/
Nicht zu lange aufhalten nur damit die mal nen Überblick bekommst,
dann gehst du an dein Projekt.
Bei OOP geht es um Grunde darum das mentale Modell, das ein Mensch von
einem Vorgang hat, im Code abzubilden. Das erhöht die Verständlichkeit
und Wartbarkeit von Code und am Ende auch die Benutzbarkeit durch den
Anwender. Das ist im Grunde eine der Kernaufgaben eines
Software-Entwicklers und die Grundlage von Objektorientierung: Das
mentale Modell des Anwenders in der Software abzubilden.
Meist wird allerdings weniger objektorientiert, sondern eher
"klassenorientiert" programmiert. Also für jeden Mist eine Klasse und
zum Selbstzweck völlige Eskalation mit Vererbung, Polymorphie, Kapselung
und so weiter. Mit dem Ergebnis, dass sich niemand mehr im Code
auskennt. Und der Benutzer wird erst recht nicht verstehen was die
Software macht, weil das Interface irgendwann später schnell-schnell
darüber gestülpt wird.
P. S. schrieb:> Bei OOP geht es um Grunde darum das mentale Modell, das ein Mensch von> einem Vorgang hat, im Code abzubilden.
Wir haben doch schon besprochen, dass das nicht funktioniert (siehe
Kreis-Ellipse-Problem). Und ist eine Funktion nicht ein besseres Modell
für einen "Vorgang"?
Das ist keine Kritik an OOP, nur an dieser Vorstellung von OOP als
"mentales Modell" der Wirklichkeit.
Wühlhase schrieb:> Ich kenne Python nicht, aber wenn die Sprache halbwegs etwas taugt dann> sollten die Beispiele daraus auch auf Python anwendbar sein.
Wenn die Sprache etwas taugt (also nicht Java), dann sollte man keine
dieser Entwurfsmuster mehr brauchen.
Dirk K. schrieb:> Der TO steigt gerade in die OOP ein und du kommst> hier mit einem der kompliziertesten Probleme der> OOP
Nicht wirklich, sondern dem üblichen Problem: übermässige Klassenanzahl.
MaWin schrieb:> Nicht wirklich, sondern dem üblichen Problem: übermässige Klassenanzahl.
Nein, einfach nochmal lesen, worauf ich mich bezogen habe.
Da steht nichts von Klassenanzahl.
Jaa, auch Trollen will gelernt sein.
merciless
Dirk K. schrieb:> Da steht nichts von Klassenanzahl
Aber ein Problem, wie es durch übermässige Klassenanzahl hervorgerufen
wird.
Das übliche Problem.
Es ist Unsinn, bei OOP versuchen zu wollen "die Realität" mit möglichst
filigraner Klasseneinteilung erfassen zu wollen. Dann läuft man
unweigerlich früher oder später in eine Sackgasse weil in der Realitat
eben nicht alles logisch ist.
Man darf und sollte eine neue zusätzliche Klasse nur dann anlegen, wenn
es aus programmtechnischen Gründen handfeste Argumente dafür gibt.
Viele Programme sind beispielsweise mit einer einzigen Klasse "die App"
gut bedient. Das ist fast wie programmieren ohne Klassen, in dem alle
sonst als global deklarierten Variablen nun Membervariablen werden und
alle Funktionen zu Methoden dieser Klasse werden die natürlich auf alle
Members zugreifen können. Man sollte diese Umwandlung eines bisher
prozeduralen Programms machen (oder gleich so starten), wenn man im
selben Kontext mehrere Instanzen davon laufen lassen will.
Es gibt dann also einen handfesten Grund: man kann nicht dieselbe
globale Variable doppelt haben, und OOP ist hier die naheliegende und
übersichtlich strukturierte Lösung.
Ebenso bei weiteren Schritten: ich will Fehlerbehandlung durch
Exceptions haben, dann darf ich nicht einfach aus einer Funktion
herauspoltern die dynamisch Speicher allokiert hat, sondern ich muss
diese Speicherallokation in eine Klasse verpacken deren Destruktor den
Speicher wieder frei gibt. Auch hier gibt es einen handfesten Grund
warum man eine Klasse (oder hunderte..) einführt.
etc. Man hinterfrage bei jeder Klasse, ob es wirklich aus der
Programmierrealität heraus einen handfesten Grund gibt, sie einzuführen.
Das schlimmste was man tun kann ist beispielsweise, für jedes
Datenbankfeld "Anrede", "Vorname", "Nachname", Strasse" ... eine eigene
Klasse einzuführen. Da verbaut man sich jede genetische Verarbeitung.
Leider führen viele OOP Kurse genau zu der Lösung, Klassen als
Selbstzweck anzulegen.
tipp schrieb:> Wenn die Sprache etwas taugt (also nicht Java), dann sollte man keine> dieser Entwurfsmuster mehr brauchen.
Aha...warum sollte Java denn nichts taugen?
Und wie sollte eine Sprache aussehen, die zwar objektorientiert ist,
aber in der keine Entwurfsmuster gebraucht werden? Was wäre in so einer
Sprache denn anders? Da bin ich doch wirklich mal gespannt...
Entwurfsmuster braucht man generell nicht. Aber sie machen einem das
Leben erheblich einfacher. Unabhängig von der Programmiersprache.
MaWin schrieb:> Nicht wirklich, sondern dem üblichen Problem: übermässige Klassenanzahl.
Was für ein Unsinn...du hast den Grund, weshalb man überhaupt in Klassen
aufteilt, absolut gar nicht verstanden. Ich gehe mal exemplarisch nur
auf eine Perle ein:
MaWin schrieb:> Man darf und sollte eine neue zusätzliche Klasse nur dann anlegen, wenn> es aus programmtechnischen Gründen handfeste Argumente dafür gibt.
Klassen hat man, um einen definierten Kontext zu schaffen. Absolut
unabhängig vom Rest des Programms.
Eine Abstraktionsebene schaffen zu wollen (z.B. weil man eine
Fremdbibliothek verwenden will und noch nicht klar ist, ob man die
später evt. durch eine andere ersetzen möchte) oder einfach einen
Funktionsteil abzugrenzen. Wenn du z.B. eine Eigenschaft eines (anderen)
Objekts hast, das du als Integer behandeln willst aber den Wertebereich
begrenzen willst.
Solche Banalitäten sind Grund genug, eine neue Klasse aufzumachen.
MaWin schrieb:> Dirk K. schrieb:>> Der TO steigt gerade in die OOP ein und du kommst>> hier mit einem der kompliziertesten Probleme der>> OOP>> Nicht wirklich, sondern dem üblichen Problem: übermässige Klassenanzahl.
Es kann gute gründe geben, für Kreise und Ellipsen andere Klassen zu
benötigen. Das Problem liegt hier nicht bei der Anzahl Klassen, sondern
bei der unnötigen/falschen Vererbung. Solange man diese hier nur nicht
direkt zwischen Kreisen und Ellipsen macht, hat man das Problem nicht
mehr.
Aber auch sonst ist die Klassenanzahl eigentlich nie das Hauptproblem.
Um noch ein anderes Beispiel zu bringen, eine Person hat eine Adresse.
Es wird nicht schaden, die Adresse in eine extra Klasse auszulagern. Die
Person sollte nur weiterhin nicht von Adresse erben. Ich kann da
beliebig mehr Eigenschaften zusammen in Klassen auslagern, es mag
weniger übersichtlich werden, aber es kann nicht so grundlegend falsch
werden, wie das mit dem Einführen einer neuen Vererbung möglich wäre.
🐧 DPA 🐧 schrieb:> Aber auch sonst ist die Klassenanzahl eigentlich nie das Hauptproblem.> Um noch ein anderes Beispiel zu bringen, eine Person hat eine Adresse.> Es wird nicht schaden, die Adresse in eine extra Klasse auszulagern. Die> Person sollte nur weiterhin nicht von Adresse erben. Ich kann da> beliebig mehr Eigenschaften zusammen in Klassen auslagern, es mag> weniger übersichtlich werden, aber es kann nicht so grundlegend falsch> werden, wie das mit dem Einführen einer neuen Vererbung möglich wäre.
Jetzt hör' doch bitte mal mit Deinem Kreuzzug gegen die Vererbung auf.
Kein denkender Entwickler der Welt käme auf die irre Idee, eine
Personenklasse von einer Adressenklasse erben zu lassen. Reale Personen
haben nämlich bisweilen mehrere Adressen, anders gesagt: die
Personenklasse beinhaltet eine Liste von Adressen. So würde man das ja
auch in einer Datenbank modellieren, als 1:n.
🐧 DPA 🐧 schrieb:> Es sind dennoch echte Entwickler, die den Fehler machen. Ausserdem ist> es ein Fehler, welcher Anfängern gerne unterläuft.
Köche schneiden sich im Laufe ihres Lebens öfter in die
Finger als andere Menschen, außerdem passiert das jungen
Köchen häufiger: Lasst uns Messer verbieten und die
Schweinekeule im ganzen über das Feuer hängen, dann
kann dies nicht mehr passieren.
merciless
Es ist sinvoll, jemanden, der zum ersten mal Kocht darauf Hinzuweisen,
dass das Messer scharf ist. Ausserdem gibt man Kleinkindern bewusst noch
keine Messer in die Hand, sondern erst denjenigen, die Messer bereits
verstehen und vermutlich damit umgehen werden können.
mh schrieb:> Wir haben doch schon besprochen, dass das nicht funktioniert (siehe> Kreis-Ellipse-Problem).
Ja. Wenn man sich auf mathematische/theoretische Spitzfindigkeiten statt
auf die eigentliche Anwendung und den Benutzer konzentriert, kann man
durchaus die Meinung vertreten, "dass das nicht funktioniert".
mh schrieb:> P. S. schrieb:>> Bei OOP geht es um Grunde darum das mentale Modell, das ein Mensch von>> einem Vorgang hat, im Code abzubilden.P. S. schrieb:> mh schrieb:>> Wir haben doch schon besprochen, dass das nicht funktioniert (siehe>> Kreis-Ellipse-Problem).>> Ja. Wenn man sich auf mathematische/theoretische Spitzfindigkeiten statt> auf die eigentliche Anwendung und den Benutzer konzentriert, kann man> durchaus die Meinung vertreten, "dass das nicht funktioniert".
Ich kann nichts dafür, dass sich mein "mentales Modell" im Allgemeinen
gut mit der Mathematik verträgt.
Das Kreis-Ellipse Problem hat man immer, wenn die abgeleitete Klasse
einschränkungen, eine fehlende Aktion oder Eigenschaft gegenüber der
Basisklasse hat. Mit Mathematik hat das nichts zutun. Ist ein Pinguin
ein Vogel?
Pinguine fliegen aber! Halt in einem anderen Medium.
mh schrieb:> Ich kann nichts dafür, dass sich mein "mentales Modell" im Allgemeinen> gut mit der Mathematik verträgt.
Mag gerne sein.
Man kann mit solch einer theoretischen Frage die Praxis auch
verkomplizieren. Nicht das es nicht wertvoll wäre das
Kreis-/Ellipse-Problem zu kennen, in der Praxis wird einem dieses
Problem aber wohl er nicht so oft begegnen und wenn, wird man es - der
Anwendung gerecht - umschiffen können.
OOP ist ein Konzept, um vieles besser verständlich, einfacher zu
implementieren,... usw. usf. zu machen. Man kann das Konzept aber auch
versuchen mit aller Gewalt jedem Problem aufzusetzen, feststellen dass
das nicht immer so gut funktioniert und dann das ganze Konzept doof
finden...
🐧 DPA 🐧 schrieb:> Das Kreis-Ellipse Problem hat man immer, wenn die abgeleitete Klasse> einschränkungen, eine fehlende Aktion oder Eigenschaft gegenüber der> Basisklasse hat. Mit Mathematik hat das nichts zutun. Ist ein Pinguin> ein Vogel?>
Matthias S. schrieb:> Pinguine fliegen aber! Halt in einem anderen Medium.
Und "wahr" ist "falsch"! Nur halt in negativer Logik.
> OOP ist ein Konzept, um vieles besser verständlich,> einfacher zu implementieren,... usw. usf. zu machen.
Der Kritikpunkt war aber nicht OOP allgemein, sondern
die VERERBUNG.
Dirk K. schrieb:> Die Annahme, dass alle Vögel können fliegen,> ist falsch (und das weisst du genau).
Kann es sein, dass Du Daniels Beispiel nicht
verstanden hast?
Egon D. schrieb:> Matthias S. schrieb:>>> Pinguine fliegen aber! Halt in einem anderen Medium.>> Und "wahr" ist "falsch"! Nur halt in negativer Logik.
Was aber nichts daran ändert, dass der Pinguinexperte von einem Flug
spricht. Was kommt jetzt rein in das Programm? Das was der
Pinguinexperte oder das was der Programmierexperte sagt?
Egon D. schrieb:> Der Kritikpunkt war aber nicht OOP allgemein, sondern> die VERERBUNG.
Dann ersetze halt OOP mit "Vererbung".
Egon D. schrieb:> Kann es sein, dass Du Daniels Beispiel nicht> verstanden hast?
Nein, was ich schon am 09.09. kritisiert habe:
Daniel konfrontiert einen Einsteiger in Sachen OOP
mit einem der kompliziertesten Design-Probleme in
der OOP. Und der Einsteiger wird nicht verstehen
können, um was es da geht. Ich sage: Lauf mal los
und designe Klassen, wie du meinst. Irgendwann wird
er eine Basisklasse Vogel mit einer Methode fliege()
haben und den Pinguin implementieren wollen:
DANN wird er erkennen, dass es ein Problem ist und
genau DANN kann man darüber diskutieren.
Ich zähle mich zu den Puristen: Ableitungen nur von
Interfaces und abstrakten Basisklassen (letzteres
sehr selten) - Exception-Klassen sind eine Ausnahme.
https://en.wikipedia.org/wiki/Composition_over_inheritance
merciless
Dirk K. schrieb:> Egon D. schrieb:>> Kann es sein, dass Du Daniels Beispiel nicht>> verstanden hast?>> Nein, was ich schon am 09.09. kritisiert habe:> Daniel konfrontiert einen Einsteiger in Sachen OOP> mit einem der kompliziertesten Design-Probleme in> der OOP.
Nein, überhaupt nicht.
Daniel versucht nur daraufhinzuweisen, dass dieses
angeblich so komplizierte OOP-Problem nur eine
zwangsläufige, aber abstruse Folgerung aus einer
(m.E.) völlig abseitigen Auffassung von OOP ist.
Wer in Automatentheorie beschlagen ist, kann sich
leicht überlegen, dass "Objekte" (im Sinne der OOP)
einfach Automaten sind; die zur Programmlaufzeit
aufgebaute Objekthierarchie ist also einfach als
Automatennetz anzusehen.
Vererbung ist für das Grundverständnis dieses
Sachverhaltens erstmal nicht erforderlich.
Vererbung mag eine nützliche Idee sein, das will
ich nicht beurteilen, aber sie gehört ganz sicher
nicht zum logischen Kern der OOP.
> Und der Einsteiger wird nicht verstehen können,> um was es da geht.
Das ist aber nicht schuld des Einsteigers, sondern
einer völlig verqueren Auffassung von OOP. :)
> Ich sage: Lauf mal los und designe Klassen, wie> du meinst. Irgendwann wird er eine Basisklasse> Vogel mit einer Methode fliege() haben und den> Pinguin implementieren wollen: DANN wird er> erkennen, dass es ein Problem ist und genau> DANN kann man darüber diskutieren.
Naja, in der Datenbanktheorie hat man relativ
frühzeitig verstanden, dass Hierarchien bzw.
Bäume nicht das alleinseligmachende Rezept für
die Strukturierung von Dingen und ihren
Eigenschaften ist, und hat die Normalformen-
theorie entwickelt.
Insofern wundert mich nicht wirklich, dass sich eine
Vererbungs- HIERARCHIE nicht als praxisgerecht erweist.
Genau DAS ist m.E. auch der Kern von Daniels Kritik:
Dass nämlich die Vererbung nicht als technisches
Hilfsmittel dargestellt wird, sondern als einfacher
und offensichtlicher Weg, die Verhältnisse des realen
Lebens im Computer abzubilden -- und genau letzteres
stimmt halt nicht.
Egon D. schrieb:> Insofern wundert mich nicht wirklich, dass sich eine Vererbungs-> HIERARCHIE nicht als praxisgerecht erweist. Genau DAS ist m.E. auch der> Kern von Daniels Kritik:
Vielleicht würde man heute statt von Vererbung eher von Teilen oder
Plagiat sprechen.
Kein "ist ein" sondern eher "kann auch" bzw. "hat auch".
Die ganze "Uhu ist ein Vogel" Metapher scheitert einfach daran, dass es
die platonische "Idee" eines Vogels nicht gibt, auch nicht die
Unterscheidung in akzidenz und Essenz.
Ein "Vogel" ist einfach nur ein willkürliches subset der realen
Ausprägungen aller Vögel.
🐧 DPA 🐧 schrieb:> Das Kreis-Ellipse Problem hat man immer, wenn die abgeleitete Klasse> einschränkungen, eine fehlende Aktion oder Eigenschaft gegenüber der> Basisklasse hat. Mit Mathematik hat das nichts zutun. Ist ein Pinguin> ein Vogel?>
1
>forvogelinvoegel:
2
>vogel.takeoff()
3
>
Ich fürchte, der ganze Denkansatz ist ein bisschen... zu kurz gedacht,
und dies gilt sowohl für das Beispiel mit den Pinguinen, als auch für
das Kreis-Ellipsendings. Natürlich, für den Biologen ist der Pinguin ein
Vogel und für den Mathematiker der Kreis eine Sonderform der Ellipse.
Es gibt aber keinen Grund der Welt, warum ein Entwickler oder
Softwaredesigner sich daran orientieren, also ein Pingu von BaseVogel
oder der Kreis von der Ellipse erben muß. Ein kluger Entwickler würde
sowas entweder beim Erstentwurf oder spätestens beim Refactoring anders
modellieren, für den Vogel also:
1
BaseVogel
2
| |
3
| +- LaufVogel
4
| +- Pinguin
5
| +- Strauß
6
| +- Emu
7
| +- Nandu
8
| +- Kiwi
9
|
10
+- FlugVogel
11
+- Amsel
12
+- Drossel
13
+- Fink
14
+- Star
Der Grundfehler bei Deinem Vögelbeispiel liegt darin, anzunehmen, daß
alle Vögel fliegen können. Genau das ist aber nicht der Fall, wie Du
meinem Modell entnehmen kannst.
Nebenbei: in Programmiersprachen, die ihre Entwicklergemeinde nicht für
zu dämlitsch dafür halten, gibt es natürlich auch noch die Möglichkeiten
der Mehrfachvererbung. Damit könnte man einen Vogel zum Beispiel einfach
als nicht schwimm-, tauch- und flugfähigen Vogel definieren, und die
Flug-, Schwimm- und Tauchfähigkeiten als weitere Klassen. So würde unser
Pinguin von BaseVogel, SchwimmMixin und TauchMixin erben, unsere Ente
dagegen von BaseVogel, Flug- und SchwimmMixin, und die Amsel von
BaseVogel und FlugMixin. Alles absolut sauber modelliert, und je nach
Anforderungen (und ggf. globaler oder lokaler Konfiguration) kann ein
Aufruf der Methode takeoff() für nicht flugfähiges Geflügel entweder
eine Exception werfen, oder die Eigenschaft Höhe auf einen Wert != 0
setzen und eine 0 zurückgeben, oder... ja, ganz genau.
Viel wichtiger, als OO-Einsteiger vor den Gefahren und Fallstricken der
OO zu warnen, ist es daher IMHO, ihnen zu raten, keine Angst vor einem
Refactoring zu haben, wenn die gewählte Architektur sich als suboptimal
und nicht zu den Anforderungen passend entpuppen sollte. Es kostet
nämlich auf mittlere und lange Sicht ein Vielfaches, um ein verkorkstes
Design herumzuentwickeln, als seine ursprünglichen Designfehler
beizubehalten.
Da wir hier jetzt ins akademische Abdriften:
So in etwa wäre mein erster Entwurf. So etwas wie
die abstrakte Basisklasse Bird würde ich auch nur
dann einbauen, wenn das technisch notwendig sein
sollte. Ich gehe hier auch davon aus, dass es
nicht unbedingt wichtig ist, auf welchem Tier
Methoden aufgerufen werden, sondern dass immer
auf den Interfaces operiert wird. An der Klasse
Tuna kann man auch erkennen, dass es Methoden gibt,
die für Vögel und Fische Sinn machen können.
1
interface IEggLayingAnimal
2
{
3
void LayEgg();
4
}
5
6
interface IFlyingAnimal
7
{
8
void Fly();
9
}
10
11
interface ISwimmingAnimal
12
{
13
void Swim();
14
}
15
16
abstract class Bird
17
{
18
private Color FeatherColor;
19
}
20
21
class Penguin : Bird, IEggLayingAnimal, ISwimmingAnimal
Jemand schrieb:> Ein kluger Entwickler würde> sowas entweder beim Erstentwurf oder spätestens beim Refactoring anders> modellieren, für den Vogel also:
Ja, kann man machen. Das teilt die Vögel in flugfähige und -unfähige.
Nur leider gibt es dutzende "Teilungen", die genauso Sinn machen und
quer durch diese Teilung laufen. Tag-Nacht, Tier- oder Planzenfresser,
Laufen oder Hüpfen, bauen Nester oder nicht … und das alles mit einem
Graubereich dazwischen.
Das ist der Grund, warum das nur in akademisch einfachen Beispielen
funktioniert.
In der Praxis brauchst Du dann doch eine Überklasse Vögel (alle
"fliegen", wenn auch manche nur vor die Füße) oder nur eine Basisklasse
(2 Beine, legen Eier, aber können weder fliegen noch laufen oder hüpfen)
Man kann aber auch einfach gucken, was man braucht.
Wenn man bei den Vögeln z.B. "fliegen" nicht braucht, sondern nur "lege
Ei", warum sollte ich mir dann den Stress um das "fliegen" machen? Weil
das in ferner Zukunft, evtl., u.U., vllt., irgendwann einmal, gebraucht
werden könnte?
Nunja, oft hat man die umgekehrte Situation. Man hat viele Flugfähige
Vögel, und irgendwo ist Logik, die bei Vögeln die Flugfunktion aufruft.
Wenn dann alles schon fertig ist kommt jemand, und will noch einen
Pinguin, einen Dodo und einen Kiwi haben...
🐧 DPA 🐧 schrieb:> Nunja, oft hat man die umgekehrte Situation. Man hat viele Flugfähige> Vögel, und irgendwo ist Logik, die bei Vögeln die Flugfunktion aufruft.> Wenn dann alles schon fertig ist kommt jemand, und will noch einen> Pinguin, einen Dodo und einen Kiwi haben...
und eine Fledermaus...
merciless
Dirk K. schrieb:> 🐧 DPA 🐧 schrieb:>> Nunja, oft hat man die umgekehrte Situation. Man hat>> viele Flugfähige Vögel, und irgendwo ist Logik, die>> bei Vögeln die Flugfunktion aufruft. Wenn dann alles>> schon fertig ist kommt jemand, und will noch einen>> Pinguin, einen Dodo und einen Kiwi haben...>> und eine Fledermaus...
... und dann noch eine Fliege, eine Hornisse, einen
Lenkdrachen, ein Luftschiff und einen Jagdflieger?
Langsam wird es albern.
Matthias S. schrieb:> Man kann aber auch einfach gucken, was man> braucht.
Ähh... ja?!
> Wenn man bei den Vögeln z.B. "fliegen" nicht braucht,> sondern nur "lege Ei", warum sollte ich mir dann den> Stress um das "fliegen" machen?
Musst Du nicht. Verlangt niemand.
> Weil das in ferner Zukunft, evtl., u.U., vllt.,> irgendwann einmal, gebraucht werden könnte?
Nee. YAGNI.
Die Diskussion hat sich nur daran entzündet, dass oben
jemand -- wie das in alten schlechten Büchern üblich
war -- Vererbung als einfaches und offensichtliches Mittel
zum Abbilden der Realität im Computer angepriesen hat.
Einfache Vererbung führt aber zu einer Hierarchie, und
von den Datenbanken her ist bekannt, dass die reale Welt
nur selten als Hierarchie abgebildet werden kann.
Der Pragmatiker tut genau das, was Du vorgeschlagen hast:
Er modelliert zunächst im Geiste den Teil der Realität,
der für ihn relevant ist, und überträgt diesen anschließend
in Programmform in den Rechner.
Egon D. schrieb:> Einfache Vererbung führt aber zu einer Hierarchie, und von den> Datenbanken her ist bekannt, dass die reale Welt nur selten als> Hierarchie abgebildet werden kann.
Kannst Du das bitte noch einmal in fett setzen und am besten auch noch
in die entsprechenden Kapitel hier einfügen? Treffend formuliert.
Egon D. schrieb:> ... und dann noch eine Fliege, eine Hornisse, einen> Lenkdrachen, ein Luftschiff und einen Jagdflieger?>> Langsam wird es albern.
Nee eben nicht. Genau das ist die Realität.
Deswegen operiere ich nur auf einem Interface,
dass die Methode Fly() bietet. Dann bin ich
flexibel, egal was da verarbeitet werden soll.
Vererbung hat ihre Berechtigung, sollte aber
sehr umsichtig eingesetzt werden. Meistens gibt
es bessere Lösungen. Aber das lernt man nicht
in "OOP für Dummies in 3 Tagen".
merciless
A. S. schrieb:> Egon D. schrieb:>> Einfache Vererbung führt aber zu einer Hierarchie, und von den>> Datenbanken her ist bekannt, dass die reale Welt nur selten als>> Hierarchie abgebildet werden kann.>> Kannst Du das bitte noch einmal in fett setzen und am besten auch noch> in die entsprechenden Kapitel hier einfügen? Treffend formuliert.
Richtig ist: so etwas kann mit relationalen Datenbanken nicht sonderlich
gut abgebildet werden, wobei auch das primär für relationale Datenbanken
gilt. Andere Datenbanken sind entweder ohnehin hierarchisch aufgebaut,
man denke da nur an Verzeichnisdienste wie OpenLDAP oder
ActiveDirectory, an Spezialisten wie IBMs Information Management System
oder auch die Windows Registry, an neuere NoSQL-Datenbanktypen wie
MongoDB oder RestDB oder an Grafendatenbanken wie OrientDB oder Neo4j.
Allerdings ist es in relationalen Datenbanken zwar ein wenig aufwändiger
und manchmal nicht besonders elegant, aber auch dort kann man mit
hierarchischen Datenmodellen arbeiten -- allein Joey Celko hat viele
spannende Dinge zu Bäumen und Hierarchien in klassischen relationalen
Datenbanken geschrieben.
Fakt ist aber auch, daß nichts so einfach ist, wie es scheint: die
meisten Menschen würden es auf den ersten Blick als die wohl wichtigste
gemeinsame Eigenschaft von Vögeln bezeichnen, daß sie fliegen können.
Trotzdem sind Dodos und Pinguine zwar Vögel, aber nicht flugfähig. Und
jetzt? Sind Pinguine und Dodos jetzt keine Vögel mehr, sondern nur noch
Tiere? Oder ist vielleicht nur die zugrundeliegende Annahme falsch, daß
alle Vögel fliegen können?
Nun, am Ende hängt es immer vom konkreten Anwendungsfall ab, ob
Vererbung oder Komposition benutzt wird, welche Eigenschaften und
Methoden implementiert werden, und so weiter. Sich hier ohne Kenntnis
des Anwendungsfalls einfach mal festzulegen und die Vererbung zu einem
Antipattern zu erklären, ist genauso zu kurz gesprungen wie sie zum
Allheilmittel hochzujubeln.