So, zum Jahresende muss ich eines noch loswerden: Klassendiagramme mag ich nicht. Oder genauer: so wie sie üblicherweise benutzt werden. Lange habe ich darüber gegrübelt, woran das liegt. Jetzt weiß ich es! Sie sind undurchsichtig, weil sie zweierlei vermischen. Sie widersprechen damit einem Grundsatz zur Bewältigung von Komplexität, dem "Separation of Concerns".
So wie UML Klassendiagramme üblicherweise benutzt werden, vermischen sie "Verwandtschaftsbeziehungen" mit "Arbeitsbeziehungen". Sie mengen Vererbungshierarchien mit Assoziationen/Aggregationen zusammen. Hier als Beispiel die übliche Darstellung des "Decorator" Entwurfsmusters:
Quelle: http://www.dofactory.com/Patterns/PatternDecorator.aspx
Wer steigt da zügig durch? Erkennen Sie auf Anhieb, was Sie zu implementieren haben? Sehen Sie gleich, wer wen nutzt? Ich nicht. Ich brauche immer eine ganze Zeit, um mich darin zurechtzufinden. Und das finde ich schlicht falsch. Ein Diagramm soll für einfache Zusammenhänge - und einfach ist das "Decorator" Pattern - auch einfach zu lesen sein.
Seitdem ich nun verstanden habe, was mich an den üblichen Klassendiagrammen stört, betreibe ich auch bei ihnen strikte Zwecktrennung oder "Separation of Concerns". Hier zur Erläuterung meine Version des "Decorator" Entwurfsmusters:
1. Arbeitsbeziehungen darstellen
Am wichtigsten finde ich es, die Arbeitsbeziehungen zwischen Klassen bzw. ihren Instanzen darzustellen. Welche Objekte brauchen welche anderen? Welche Objekte enthalten (aggregieren) welche anderen? Und das alles mit den richtigen Mengenangaben (Beziehungskardinalitäten: 1:1, 1:n, n:m).
Darauf zunächst reduziert sieht das "Decorator" Pattern so schön einfach aus:
Alles klar? Ich hoffe! Applikationscode ruft entweder Instanzen einer "Simple Class" direkt auf oder nutzt Objekte von "Enhanced Class", hinter denen jeweils eine "Simple Class"-Objekt steht.
Da Klassen bzw. Vererbung nicht einmal zum Kern der Objektorientierung gehören, ist die Darstellung von Vererbungsbeziehungen auch nicht essenziell. In Bezug auf den "Decorator" bedeutet das: Wesentlich ist nicht, welche Klassen hier gemeinsame "Vorfahren" haben, sondern dass der "Decorator" (Enhanced Class) genauso aussieht (!) wie das zu Dekorierende (Simple Class). Das lässt sich aber schon an den gleichen Methodennamen ablesen.
2. Zusammenarbeit beschreiben
Wenn die Darstellung der Arbeitsbeziehungen bzw. des Objektgeflechts noch Fragen offen lässt, dann untermauere ich sie mit einem Sequenzdiagramm. Die Nutzung eines Decorators sieht dann zum Beispiel so aus:
Alles klar? Ich hoffe! Denn auch hier liegt der Fokus auf dem, was am Nutzungsort in der Applikation relevant ist. Dort hat der Code mit dem Decorator und dem Dekorierten zu tun - und nicht mit irgendwelchen möglichen Basisklassen.
3. Verwandtschaftsbeziehungen darstellen
Erst ganz zum Schluss dokumentiere ich dann die Verwandtsschaftsbeziehungen zwischen den funktional relevanten Klassen. Dann geht es um Ableitungen oder Implementationen von Interfaces. Und dann beschreibe ich womöglich sogar Alternativen. Bei "Decorator" Pattern sind das z.B. diese:
Alles klar? Ich hoffe! Denn eine solche Darstellung, die sich auf Vererbung konzentriert, ist doch leicht zu verstehen.
In der Literatur findet sich übrigens nur die untere Ableitungsstruktur. Aber ich denke, eine Basisklasse für alle Decorators ist nicht nötig. Wesentlich ist lediglich, dass Domänenklasse/zu Dekorierendes und Decorator eine gemeinsame Herkunft haben. Die macht sie für den Applikationscode dynamisch austauschbar.
Fazit
Nachdem ich meine Software-Detailentwürfe von "vermengenden" Klassendiagrammen auf "separierte" Klassendiagramme umgestellt habe, machen mir objektorientierte UML-Darstellungen wieder deutlich mehr Spaß. Ich kann Ihnen also nur empfehlen, grundsätzlich Verschiedenes wie Arbeitsbeziehungen und Verwandtsschaftsbeziehungen ebenfalls zu so zu trennen. Sie machen Ihre Objektemodell viel besser verständlich.
5 Kommentare:
Wie gehen denn aktuell UML-Designer oder konkret Model-Driven-Designer mit dieser Dualität um? Die wenigsten können doch MDD und UML2 gleichzeitig - da neige ich zu Schwarzmalerei.
Hast Du Erfahrungen mit MDD gesammelt?
Wie gehen Tools mit der Dualität um? Sie unterscheiden nicht. Zumindest nicht, dass ich wüsste. Das beklage ich ja gerade. Die Dualität und Wichtigkeit von Ableitungen steckt auch viel zu tief in OOP drin.
Aber ich gehe halt bei meinen Entwürfen nicht streng nach OOP vor. Mich interessieren Klassen da erst sehr spät. Und möchte ich halt trennen - und tue es auch.
Von MDD halte ich nicht viel. Große Vision - zu große Vision für mich.
-Ralf
Ich finde das Klassenmodel des "Decorator" Entwurfsmusters in Ordnung. Dagegen kann ich dem nicht zustimmen, dass das reduzierte Klassenmodel zu dem "Decorator" Entwurfsmuster die Komplexität einschränkt, im Gegenteil es eliminiert wichtige Informationen wie Vererbungshierarchie der Klassen untereinander weg!
Die Aussage: "Da Klassen bzw. Vererbung nicht einmal zum Kern der Objektorientierung gehören, ist die Darstellung von Vererbungsbeziehungen auch nicht essenziell" würde ich nochmal überdenken, denn ohne die Vererbung wäre nicht mal die Verwendung von Interfaces möglich gewesen. Selbstversätndlich gehört die Verwerbung zum Kern der OOP.
Viele Grüße
@Gabriele: Jede Abstraktion lässt Details außen vor. Das ist ja ihr Sinn. Eine Landkarte ist nicht die Landschaft.
Gerade weil die Vermischung von Vererbung und sonstigen Beziehungen soviele Details enthält, halte ich eine Separation für angezeigt (außer in trivialen Fällen).
Ob Vererbung zur Kern der Objektorientierung gehört, habe ich schon überdacht. Interfaces haben damit auch nichts zu tun. Interfaces beschreiben "hat"- oder "verhält sich wie"-Beziehungen. Vererbung beschreibt eine "ist ein"-Beziehung. Schon deshalb sind die beiden Konzepte orthogonal.
Die Formulierung "eine Klasse erbt ein Interface" ist insofern umgangssprachlich und nicht korrekt. Sie ist - meiner Meinung nach - das Resultat einer Homonymie: Ableitung (von einer Klasse) und Implementation (eines Interfaces) werden mit demselben sprachlichen Mittel ausgedrückt. Ein ":" trennt Klassenname von Basisklasse wie auch Interface und suggeriert insofern "Ererbung" von einem Interface. Das ist in der Sache aber falsch. Erben kann man nur, was man ist (Genotyp) und nicht, wie man aussieht (Phänotyp).
Davon abgesehen halte ich Vererbung immer noch nicht zum Kern von OOP gehören. Zwar ist sie in allen OO-Sprachen enthalten, wenn ich mich nicht irre, aber das bedeutet noch nicht, dass sie zum Kern des Paradigmas gehört. Alle OO-Sprachen bieten auch Parameter oder for-Schleifen. Diese Features gehören deshalb auch nicht zum OO-Paradigma.
Oder ein anderes Beispiel: Heute GPL sind alle Turing complete Sprachen. Sie haben alle eine for-Anweisung. Gehört die for-Anweisung zur Turing completeness? Nein. Sie ist ein convenience feature.
Auf derselben Ebene sehe ich die Vererbung. Sicherheit geben mir dabei die Worte der Warnung bzw. die Betonung anderer Konzepte bei der GoF. Und ich habe neulich James Gosling persönlich danach gefragt, ob er "beim nächsten Mal" Java wieder mit Vererbung ausstatten würde. Seine Antwort war sehr zurückhalten bis ablehnend gegenüber der Vererbung.
Insofern: Wenn diese Autoritäten die Vererbung so konservativ bis ablehnend bewerten, warum sollte ich also meine Meinung nochmal überdenken?
-Ralf
Vererbung und Interfaces sind nicht das gleiche, das ist klar.
Das Konzept der Vererbung ist historisch länger in OOP Sprachen enthalten (seit Smalltalk), mit Java kam erst das Konzept der Interfaces.
Damals lies sich das Konzept des Subtypings durch die Technik der Vererbung realisieren.
Das war als ein Denkanstoß gedacht.
Tschüß
Kommentar veröffentlichen