Keylearnings:
- Wie du Beziehungen zwischen Klassen mit einem UML Klassendiagramm darstellst.
- Was ist bei einem objektorientierten Design zu beachten?
- Wie du Attribute und Methoden im Klassendiagramm darstellst.
- Was ist Multiplizität?
- Wie du Beziehungen zwischen Klassen darstellst.
- Was ist der Unterschied zwischen Aggregation und Komposition?
- Wie du Vererbung im UML Klassendiagramm darstellst.
Wir wissen es bereits.
Objektorientierte Programmierung ist der Himmel!
Außer du machst es falsch, dann landest du in der Hölle!
Und dahin wollen wir ganz sicher nicht!
Bevor du in die Tasten haust solltest du dir also unbedingt ein paar Gedanken machen.
Gerne kannst du das im Garten bei einer kühlen Cola tun. Hauptsache du hast einen Schreibblock und einen Bleistift mit Radiergummi zur Hand.
Oder du verwendest die kostenlose Software UMLet.
Denn ein großer Vorteil des objektorientierten Designs ist, dass man die Komponenten und deren Zusammenhänge in einem Softwaresystem grafisch darstellen kann.
So wie wir es bereits hier mit einer Klasse Vierbeiner
gemacht haben.
Eine sehr smarte Möglichkeit der visuellen Darstellung von Klassen und deren Zusammenhänge ist das sogenannte UML Klassendiagramm. Hierbei steht UML für Unified Modeling Language.
Das Klassendiagramm ist ein Tool, das du dringend in dein Werkzeugkasten aufnehmen solltest.
Wie jedes Werkzeug kannst du allerdings auch das UML Klassendiagramm erst effektiv nutzen, wenn du deren Einsatzgebiet verstehst.
Deshalb wollen wir uns zunächst darüber unterhalten, über welche Dinge wir uns bei einem objektorientierten Design den Kopf zerbrechen müssen.
Objektorientiertes Design mit dem UML Klassendiagramm
Eine Klasse besteht aus drei Bestandteilen. Jede Klasse hat einen Namen, Eigenschaften (auch Attribute genannt) und Methoden.
Aus Klassen erzeugen wir konkrete Objekte (instanziieren). So wird beispielsweise aus der Klasse Hund
eine Hunde-Instanz mit dem Namen Snoopy und einem Gewicht von 20kg.
Die Attribute der Klasse beschreiben hierbei den Zustand des Objekts, wie z.B. Name und Gewicht eines Hundes.
Methoden beschreiben hingegen das Verhalten eines Objekts und geben ihm Fähigkeiten, wie z.B. dass ein Hund bellen kann.
Im UML Klassendiagramm werden diese drei Elemente durch waagerechte Striche voneinander getrennt. Für unser Hunde Beispiel sieht das Klassendiagramm wie folgt aus:
Ganz oben steht der Name der Klasse. In unserem Fall heißt die Klasse Hund.
Der mittlere Teil enthält die Klassen-Attribute. Also den Namen und das Gewicht des Hundes.
Jedes Attribut hat einen Datentyp, den man durch einen Doppelpunkt getrennt hinter den jeweiligen Attributs-Namen schreibt.
Die Methoden werden samt Parameterliste und Rückgabewert im unteren Teil des UML Klassendiagramms aufgeführt wobei der Datentyp des Rückgabewerts hinter dem Doppelpunkt steht.
Wir haben es hier mit einem ziemlich einfach gestrickten Hund zu tun. Neben der Methode bellen
enthält unsere Klasse lediglich die getter- und setter- Methoden für die Attribute.
Ist da nicht noch was?
Bestimmt ist es dir schon aufgefallen!
Wir haben den Attributen ein Minuszeichen – und den Methoden ein +Pluszeichen vorangestellt.
Wie du aus den Grundlagen der objektorientierten Programmierung weißt, sollten Instanz-Variablen, um diese gegen Manipulation zu schützen, von Außen nicht sichtbar, also als privat deklariert sein.
Der Fachmann spricht hierbei von Kapselung.
Und genau dafür steht das Minuszeichen – . Ein Attribut oder einer Methode, der wir ein Minuszeichen voranstellen ist privat deklariert, wohingegen das Pluszeichen + für ein als public deklariertes Attribut bzw. eine als public definierte Methode steht.
Selbstverständlich ist es auch möglich Attribute und Methoden als protected zu definieren.
Als protected deklarierte Attribute bzw. definierte Methoden sollen nur innerhalb der Klasse selbst und allen Unterklassen sichtbar sein.
Im UML Klassendiagramm kennzeichnen wir die Sichtbarkeit protected mit Hilfe des Hashzeichen #.
Klassenvariablen im UML Klassendiagramm
Bisher handelte es sich bei unseren Attributen immer um Instanz-variablen. Jede Instanz unserer Hunde Klasse beansprucht also einen eigenen getrennten Speicherbereich.
Was ist aber wenn wir die Anzahl der erzeugten Hunde-Objekte zählen wollen?
Für diesen Zweck benötigen wir EINE Integer-Variable, auf die alle Hunde-Instanzen Zugriff haben.
Eine solche Variable wird Klassenvariable genannt und in Java mittels des Schlüsselworts static
definiert.
Im UML Klassendiagramm werden Klassenvariablen mit Hilfe eines Unterstrichs gekennzeichnet.
Los! Lass uns das Klassendiagramm von oben um eine Klassenvariable, mit der wir die Anzahl der erzeugten Hunde zählen können ergänzen.
Das war einfach, oder? Wir mussten lediglich den Attribute-Teil des UML Klassendiagramms um eine mit Unterstrich versehende Integer-Variable hundZaehler
ergänzen.
Multiplizität im UML Klassendiagramm
Bis hierhin haben wir es uns einfach gemacht. Unsere Attribute bestehen bisher nur aus primitiven Datentypen. Was machen wir aber, wenn wir Arrays oder Array-Listen verwenden wollen?
Hierfür gibt es die sogenannte Multiplizität.
Natürlich hat unser Hund drei Lieblingsspielzeuge, nämlich Frauchen, Lego und einen Baseballschläger.
Und zwar genau in dieser Reihenfolge!
Wir benötigen also ein Array, das diese drei Elemente in der angegebenen Reihenfolge aufnehmen kann.
Hierzu schreiben wir die sogenannte Multiplizität [1..3] und das Kennzeichen {order} hinter das Attribut, das die Spielzeuge aufnimmt.
Über die Multiplizität [1..3] legen wir die Kapazität des Arrays fest. Mit dem Zusatz {order} kennzeichnen wir, dass es sich bei libelingsSpielzeug
um eine geordnete Datenstruktur handelt, bei der es auf die Reihenfolge ankommt.
Außerdem findet ein stolzer Hund jeden Tag ein neues Futter, das ihm schmeckt.
Daher benötigen wir des Weiteren eine Datenstruktur, die beliebig viele Elemente aufnehmen kann. Außerdem soll jedes Futter nur ein einziges mal, d.h. eindeutig, in der Datenstruktur gespeichert werden.
Für diesen Zweck stellt die UML die Multiplizität [*] und das Kennzeichen {unique} zur Verfügung.
Erweitern wir unser UML Klassendiagramm also ein weiteres mal.
Unser Hund kann jetzt beliebig viele Lieblingsmahlzeiten haben. Allerdings kann jedes Futter nur eindeutig im Attribut lieblingsFutter
gespeichert werden. Eine Konstellation wie: „Meine Lieblingsgerichte sind 1. Pizza, 2. Pizza und 3. Pizza“ ist wegen des Kennzeichens {unique} nicht möglich.
Konstanten im UML Klassendiagramm
Um die Wartbarkeit deiner Programme zu erhöhen solltest du Werte, die sich nicht ändern in Konstanten definieren.
Die berühmteste aller Konstanten ist Pi. Anstatt überall da wo wir mit der Zahl Pi rechnen 3.14.. hinzuschreiben, verwenden wir die Konstante Math.PI
.
Konstanten werden in Java mit Hilfe des Schlüsselwortes final
deklariert und im UML Klassendiagramm mit dem Zusatz {readOnly} versehen.
Eine oft anzutreffende Verwendung von Konstanten sind Versionsnummern. Erweitern wir unser UML Klassendiagramm also erneut.
Da die Version der Klasse für jede Instanz des Hundes die gleiche ist, handelt es sich bei der Variablen VERSION
um eine Klassenvariable, die im Klassendiagramm unterstrichen dargestellt werden muss.
Vom UML Klassendiagramm zum Programmcode
Ziel unserer Anstrengungnen ist ein lauffähiges Programm.
Alle unsere bisherigen Bemühungen bringen uns nur etwas, wenn wir das Klassendiagramm möglichst leicht in Java Quellcode übersetzen können.
Und genau hierum wollen wir uns als nächstes kümmern.
Hier der aus dem Klassendiagramm erzeugte Quellcode.
1:public class Hund { 2: private String name = null; 3: private int gewicht = 0; 4: private ArrayList lieblingsSpielzeug = new ArrayList(); 5: private HashSet libelingsFutter = new HashSet(); 6: private static int hundeZaehler = 0; 7: private static final String VERSION = "2.2.1"; 8: public void bellen(){} 9: public String getName() {} 10: public void setName(String name) {} 11: public int getGewicht() {} 12: public void setGewicht(int gewicht) {} }
In den Zeilen zwei und drei deklarieren wir die primitiven Attribute name
und gewicht
.
Anschließend verwenden wir eine Array Liste um die Lieblingsspielzeuge, und ein HashSet
um die Lieblingsspeisen unseres Hundes zu speichern.
Hierbei verwenden wir ein HashSet
, da wir wegen der Markierung durch {unique} die Mahlzeiten eindeutig in unserer Datenstruktur abspeichern müssen.
Zu guter letzt fügen wir in den Zeilen sechs und sieben noch die statische Zählervariable hundZaehler
und die Konstante VERSION
als Attribute hinzu.
In den Zeilen acht bis zwölf sind die Methoden aufgeführt.
Hieraus wird auch klar was das UML Klassendiagramm NICHT leisten kann. Das Klassendiagramm beschreibt lediglich welche Methoden eine Klasse zur Verfügung stellt. Es liefert aber keinen Hinweis darauf wie die Funktionalität dieser Methoden implementiert werden muss.
Das Klassendiagramm hilft uns also nicht dabei einen Algorithmus zu modellieren. Für diesen Zweck stellt die UML allerdings andere Diagramme wie beispielsweise das Sequenzdiagramm zur Verfügung.
So stellst du Beziehungen im UML Klassendiagramm dar
Ganz ehrlich! Bisher ist das alles nur lästig und bringt überhaupt nichts.
Eine einzelne Klasse Hund, hättest du auch ohne den ganzen Aufwand in den Rechner hämmern können.
Interessant wird es erst, wenn unser Softwaresystem aus mehr als nur einer einzelnen Klasse besteht und wir beschreiben möchten wie diese Klassen miteinander in Verbindung stehen.
Genau wie es im echten Leben, freundschaftliche, romantische oder geschäftliche Beziehungen gibt, gibt es auch in der Objektorientierung verschiedene Beziehungsarten.
Beginnen wir mit der ersten Art, nämlich den Abhängigkeiten, die in der Fachliteratur häufig auch Dependencies genannt werden.
Dependencies im UML Klassendiagramm
Ein Hund hat Hunger und frisst aus einem Fressnapf.
Der Fressnapf ist ein Objekt, das wir aus einer Klasse Fressnapf
erzeugen und fressen
ist eine Methode der Klasse Hund mit einer Fressnapf-Instanz als Parameter.
Die Fressnapf-Instanz ist kein fester Bestandteil des Hundes, sondern wird nur solange verwendet bis die Methode fressen
abgearbeitet wurde.
Eine solche Beziehung wird Verwendungsbeziehung genannt und wird im UML Klassendiagramm mit Hilfe eines mit dem Merkmal <use> beschrifteten Pfeils dargestellt.
UML Assoziationen
Warst du schon einmal in einem Tierheim?
In einem Tierheim gibt es Tiere (wer hätte das gedacht), Kaninchen, Katzen, Mäuse und auch Hunde, um die sich ein Tierpfleger kümmert.
Zwischen Hund und Tierpfleger besteht innerhalb eines Tierheims ganz offensichtlich eine Beziehung.
Allerdings ist die Beziehung nicht so stark, dass der eine nicht ohne den anderen könnte.
Sowohl ein Tierpfleger als auch ein Hund kann eigenständig existieren.
Ein Hund kann glücklich in eine Familie integriert sein und es gibt Tierpfleger, die sich nur um Knut den Eisbären kümmern.
Solch schwache Verbindungen werden mit Hilfe einer einfachen Verbindungslinie zwischen den Klassen dargestellt.
UML Aggregationen und Kompositionen
Häufig haben wir es mit Klassen zu tun, die als Attribute Instanzen anderer Klassen enthalten.
So besitzt beispielsweise das Tierheim Instanzen eines Tierpflegers und eines Hundes.
Allerdings ist auch hier wieder wichtig, dass sowohl der Pflegehund als auch der Tierpfleger ohne das Tierheim existieren können.
Der Pflegehund ist ohne das Tierheim ein noch ärmerer Hund und der Tierpfleger ist ohne Tierheim ein arbeitsloser Tierpfleger.
Aber beide sind nach wie vor existent. Eine solche Beziehung heißt Aggregation und wird mit einem Diamantenzeichen im UML Klassendiagramm gekennzeichnet.
Die Komposition!
Eine stärkere Assoziation ist die sogenannte Komposition.
Bei diesem Assoziationstyp ist die Beziehung so stark, dass mit dem Löschen des „Behälterobjekts“ auch das integrierte Objekt verschwindet.
Genau das ist bei unserem Fressnapf der Fall!
Denn werfen wir den mit Futter gefüllten Fressnapf weg, verlieren wir auch das darin enthaltene Futter.
Eine Komposition kennzeichnen wir mit einem ausgefüllten Diamantzeichen.
Wie unterscheiden sich Aggregation und Komposition in der Implementierung?
Entscheidender Unterschied zwischen Aggregation und Komposition ist die stärke der enthält Beziehung.
Schauen wir uns das an einem Beispiel an.
Hund bello = new Hund(); Tierheim heim = new Tierheim(bello);
Hier erzeugen wir eine Hunde-Instanz bello
, die wir über den Konstruktor der Klasse Tierheim
in ein Heim einquartieren.
Was passiert mit bello
, wenn wir die Tierheim Instanz löschen?
Nix! Denn die Instanz bello
liegt in einem eigenen Speicherbereich, der unabhängig von dem Bereich, in welchem das Tierheim liegt ist.
Die Tierheim Instanz enthält lediglich eine Referenz auf das Objekt bello
.
Daher handelt es sich in diesem Fall um eine Aggregation.
Werfen wir einen Blick auf die Komposition.
Fressnapf napf = new Fressnapf(new Futter());
Hier erzeugen wir einen mit Futter gefüllten Fressnapf.
Das Futter erzeugen wir im Argument des Fressnapf Konstruktors, weshalb das Futter in dem für den Fressnapf reservierten Speicherbereich liegt.
Was passiert also mit dem Futter, wenn wir die napf
Instanz löschen?
Korrekt! Mit dem Napf verlieren wir auch das Futter, weshalb es sich in diesem Fall um eine Komposition handelt.
Vererbung im UML Klassendiagramm
Vermisst du noch was? Ja, ich auch!
Keine objektorientierte Programmierung ohne Vererbung!
Ein Hund ist ein Vierbeiner, genau wie eine Katze oder ein Elefant. Daher führen wir eine Klasse Vierbeiner
ein, in der wir die allgemeinen Eigenschaften eines Vierbeiners implementieren und davon die Tiere Hund, Katze etc. ableiten.
Im UML Klassendiagramm wird Vererbung mit Hilfe eines Pfeils dargestellt.
Vierbeiner ist Oberklasse des Hundes, in der wir die Methoden und Eigenschaften implementieren, die alle Vierbeiner gemeinsam haben.
Das Vierbeiner eine Oberklasse des Hundes ist, deuten wir mit einem zu der Klasse Vierbeiner gerichteten Pfeil an.
Theorie und Praxis
Die hier beschriebene Vorgehensweise wird Wasserfallmodell genannt.
Beim Wasserfallmodell setzten wir voraus, dass wir alle Anforderungen von Beginn an kennen und gehen außerdem davon aus, dass sich diese während des gesamten Entwicklungsprozesses nicht ändern.
In der Praxis ist diese Voraussetzung leider oft nicht erfüllt, weshalb mit einer iterativen Entwicklung gearbeitet wird, bei der die typischen Entwicklungsarbeiten wie Design, Implementierung und Tests parallel stattfinden.
In jedem Iterationsschritt werden die Anforderungen, die die Software erfüllen soll verfeinert.
Derzeit ist hier die Agile-Softwareentwicklung der Platzhirsch.
Zu guter letzt möchte ich dir im folgenden Video noch zeigen wie du das Klassendiagramm in Quellcode umsetzt.
Fazit: Auch wenn das Erstellen eines UML Klassendiagramms zunächst wie unnötiger Mehraufwand erscheint, ist es in Wirklichkeit so, dass du hiermit wertvolle Vorarbeit leistest, die dir während der Implementierung viele Fehlerkorrekturen erspart.
Hast du auch schon mit dem UML Klassendiagramm gearbeitet? Was ist deine Erfahrung? Hinterlass mir doch einfach einen Kommentar!
Hat dir der Artikel gefallen? Dann folge uns am besten gleich auf Facebook!
Bob
8. April 2018 at 18:25Hallo Kim, danke für deine ausführlichen Berichte. Ich lese immer wieder gerne hier. Mach weiter so!
Gruß
Bob
Kim Peter
8. April 2018 at 20:20Vielen Dank Bob!
Gudrun Herenbergmayer
8. Mai 2018 at 7:10Kim Peter, bin in der Schule auf deine Seite gestoßen, finde sie mega
Kim Peter
8. Mai 2018 at 9:01Vielen Dank! 🙂
Maxim
28. Mai 2018 at 8:06Hallo Herr Peter, ihr Bericht hat mir schon sehr weitergeholfen. Mich würde nur interessieren, wie ich bei den Klassen die Unterstruche für die jeweiligen Attribute mache.
Kim Peter
5. Juni 2018 at 6:18Hallo Maxim, die Unstriche bedeuten einfach nur, dass es sich um eine Klassenvariable handelt. Also Variablen, die mit Schlüsselwort static definiert sind. Viele Grüße Kim
Dominik
27. Juli 2018 at 19:00Hey Kim! Einen Tag vor der Klausur OOP mit C++ bin ich über Deine Seite gestoßen. Ich hätte mir viel Mühe und Verzweiflung sparen können, wenn mir jemand UML an einem so anschaulichen Beispiel erklärt hätte, wie Du es hier getan hast. Vielen Dank für den tollen Artikel!
Kim Peter
8. September 2018 at 19:37Hallo Dominik, vielen Dank. Ich hoffen deine Klausur ist gut gelaufen. Viele Grüße Kim
Muhammed
8. August 2018 at 13:36Hallo,
wie wird die Vererbung im Programm implementiert? Würdest du mir bitte ein Beispiel nennen.
Kim Peter
8. September 2018 at 19:44Hallo Muhammed, Vererbung implementierst du in Java mit Hilfe des Schlüsselwortes extends. Ein Beispiel findest du z.B. hier http://www.codeadventurer.de/?p=1375. Viele Grüße Kim
Eya
16. Januar 2019 at 15:52Hast du vielleicht ein Beispiel in Python ?
es ist mir schwer Klassendigramme(mit verschiedenen Beziehungselemente) in Python zu setzen 🙁
Kim Peter
17. Januar 2019 at 7:17Hallo Eya, das Thema habe ich bisher leider noch nicht im Programm. Viele Grüße Kim
Abel
21. Januar 2019 at 20:43Super!!
Danke
Kim Peter
22. Januar 2019 at 7:23Gerne!
Justine Hermann
23. Januar 2019 at 14:59Hallo Kim,
ich finde Deine Seite voll supi!!!!! 😉
Gerne lerne ich mehr von Dir!!
Liebe Grüße
Deine Justine
Kim Peter
23. Januar 2019 at 15:17Hallo Justine, das freut mich sehr! Viele Grüße Kim
alice
18. Februar 2019 at 19:28will nicht den grammarnazi spielen, aaaaaaber „behältst“ wär schon besser
(nicht böse gemeint xD)
Kim Peter
18. Februar 2019 at 20:03Absolut richtig. Habe die Überschrift geändert. Danke für den Hinweis. Viele Grüße Kim
Halil
12. März 2019 at 13:30Danke Kim, ich lerne auch für eine Prüfung(Modellierung und Design Patterns) und wie du alles hier erklärt hast finde ich einfach super verständlich!
Kim Peter
13. März 2019 at 7:37Ich wünsche dir viel Erfolg für deine Prüfungen!
Burat Muhalla
28. Juni 2019 at 9:03Du wirst sicherlich ein großartiger Programmierer, vielleicht sogar einer der besten unserer Zeit. Ich liebe deine Beiträge, und küsse deine Augen.
Kim Peter
2. Juli 2019 at 11:08Danke! 😉
Jörg
24. Februar 2020 at 14:14Hallo Kimm,
bin zufällig auf deine Seite gestoßen und bin begeistert wie toll und verständlich du die einzelnen Themen erklärst.
Werde in nächster Zeit öfters bei dir vorbei schauen.
Vielen Dank für deine tolle Arbeit
Gruß
Jörg
Kim Peter
19. Mai 2020 at 18:34Hallo Jörg, danke das freut mich. Viele Grüße Kim
Lara
9. März 2020 at 12:27Hi, super Beitrag, doch mir ist aufgefallen, dass fressen(Napf : Fressnapf) keinen Rückgabewert hat 🙂 müsste doch void sein?
Liebe Grüße,
Lara
Kim Peter
19. Mai 2020 at 18:32Hallo Lara, danke und ja du hast recht. Viele Grüße Kim
Choose a style: