Assoziationen: Unterschied zwischen den Versionen
Zeile 138: | Zeile 138: | ||
In jedem Fall muss dafür gesorgt werden, dass das Objekt, dessen Attribute oder Methoden genutzt werden sollen (objekt 2), dem nutzenden Objekt (objekt 1) bekannt ist, d.h. ''objekt 1'' muss über eine '''Referenz''' auf ''objekt 2'' verfügen. | In jedem Fall muss dafür gesorgt werden, dass das Objekt, dessen Attribute oder Methoden genutzt werden sollen (objekt 2), dem nutzenden Objekt (objekt 1) bekannt ist, d.h. ''objekt 1'' muss über eine '''Referenz''' auf ''objekt 2'' verfügen. | ||
+ | |||
+ | |||
+ | '''Richtung der Assoziation''' | ||
+ | |||
+ | Für die Form der Implementierung ist zunächst die Richtung der Beziehung ausschlaggebend: Nutzt nur eines der beiden an der Beziehung beteiligten Objekte die Attribute der Methoden des anderen oder findet diese Nutzung in beiden Richtungen statt? Im ersten Fall handelt es sich um eine '''unidirektionale''', im zweiten um eine '''bidirektionale''' Assoziation. | ||
+ | |||
+ | Außerdem ist für die Umsetzung in ein Programm sehr wichtig, wie viele Objekte der einen Klasse durch die Assoziation jeweils jedem Objekt der anderen Klasse zugeordnet werden. Dies wird durch die sogenannte '''Multiplizität''' der Assoziation ausgedrückt. Der einfachste Fall liegt bei einer '''1:1'''-Assoziation vor: Einem Objekt der einen Klasse wird genau ein Objekt der anderen Klasse zugeordnet. In diesem Fall genügt ein einzelnes Attribut zur Aufnahme der Referenz. | ||
<div style="background-color:#BEE599; padding:10px; margin-bottom:30px;"> | <div style="background-color:#BEE599; padding:10px; margin-bottom:30px;"> | ||
Zeile 204: | Zeile 211: | ||
</popup> | </popup> | ||
</div> | </div> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Version vom 22. Mai 2019, 10:29 Uhr
Am Ende des Kapitels solltest du
- die Begriffe Aggregation, Komposition, Assoziation und Multiplizität kennen,
- die Funktion einer Referenzvariable nachvollziehen können,
- mit dem Datentyp Arraylist und den Methoden dieser Klasse umgehen können,
- Klassenvariablen und Klassenmethoden kennen und anwenden können.
In vielen Objekt- bzw. Klassenmodellen begegnet man Objekten, die wiederum Objekte anderer Klassen enthalten. Eine solche "Enthält-Beziehung" (Aggregation) wird in Programmobjekten durch ein Attribut implementiert, als dessen Typ die Klasse der enthaltenen Objekte angegeben wird. Die Aggregation spezifiziert eine Assoziation zwischen Objekten. Im Gegensatz zur Komposition (die ebenfalls eine „ist-Teil-von“-Assoziation beschreibt) kann das Teil-Objekt ohne das Aggregat-Objekt existieren. In der UML wird die Aggregation durch eine leere, die Komposition durch eine ausgefüllte Raute auf der Seite der Aggregat-Klasse symbolisiert.
Beispiel für Aggregation: Eine Ehe besteht aus zwei Ehepartnern, die auch nach einer Scheidung der Ehe als eigenständige Personen fortbestehen.
Beispiel für Komposition: Ein Gebäude besteht aus Stockwerken bzw. Räumen, die nach dessen Abriss nicht eigenständig fortbestehen.
Aufgabe 1
Es soll eine Ampelsystem modelliert werden, das aus einzelnen Ampeln besteht. Die einzelne Ampel ist wiederum aus einem Rechteck und drei Kreisen zusammengesetzt. Entscheide, ob es sich hierbei um eine Aggregation, eine Komposition oder eine Kombination beider handelt und erstelle anschließend das zugehörige Klassenmodell.
Programmiertechnisch wird eine Aggregation zwischen zwei Objekten nicht dadurch implementiert, dass ein Programmobjekt tatsächlich ein anderes enthält. Vielmehr speichert das enthaltende Objekt lediglich eine Referenz auf das enthaltene Objekt als Wert eines Attributes. Das enthaltene Objekt existiert jedoch selbstständig außerhalb des enthaltenden.
Beispiel für die Implementierung einer Aggregation
Bei bekannten Computerspielen kann ein Spieler seine Fahrkünste auf einem Formel 1-Kurs testen oder als Pilot einen Flughafen anfliegen. Dabei muss jeweils ein Spieler mit einem Kurs/Flughafen assoziiert werden. Damit ein Spieler sich auf einem Kurs befinden kann, lässt sich in Spieler eine Referenzvariable vom Typ Kurs anlegen. In Java sähe das in etwa so aus:
public class Spieler { public Kurs k; } public class Kurs { }
Im Gegensatz zu Werten einfacher Typen wie int muss ein Objekt, das als Attributwert eines anderen dienen soll, erst erzeugt werden. Dazu muss die Konstruktormethode der entsprechenden Klasse aufgerufen werden. In unserem Beispiel müssen also zuerst zwei Objekte vom Typ Spieler bzw. Kurs erzeugt werden:
public class Spiel { Spieler uschi = new Spieler(); Kurs hockenheim = new Kurs(); uschi.k = hockenheim; }
Aufgabe 2
Erstelle eine Klasse Punkt, die die Attribute x und y und entsprechende Getter- und Setter-Methoden hat, und eine Klasse Kreis mit den Attributen radius und Mittelpunkt. Die Klasse Kreis soll zwei Methoden zum Verändern des Mittelpunktes und zum Verändern des Radius erhalten. Erstelle zuerst ein entsprechendes Klassenmodell.
Oft findet man bei der objektorientierten Modellierunng eines Systems neben der Aggregation ("enthält", "ist Teil von") noch weitere Beziehungen zwischen den Objekten diverser Klassen, wie z.B. "ist Angestellter von", "benutzt", "verkauft", "reserviert". Allgemein bezeichnet man diese Beziehungen als Assoziationen.
Art der Assoziation
Eine Assoziation beschreibt immer eine bestimmte Art von Kommunikation zwischen zwei Objekten, für die es folgende Möglichkeiten gibt:
- Ein Objekt nutzt Daten (Attributwerte) eines anderen Objekts.
- Ein Objekt ruft Methoden eines anderen Objekts auf.
In jedem Fall muss dafür gesorgt werden, dass das Objekt, dessen Attribute oder Methoden genutzt werden sollen (objekt 2), dem nutzenden Objekt (objekt 1) bekannt ist, d.h. objekt 1 muss über eine Referenz auf objekt 2 verfügen.
Richtung der Assoziation
Für die Form der Implementierung ist zunächst die Richtung der Beziehung ausschlaggebend: Nutzt nur eines der beiden an der Beziehung beteiligten Objekte die Attribute der Methoden des anderen oder findet diese Nutzung in beiden Richtungen statt? Im ersten Fall handelt es sich um eine unidirektionale, im zweiten um eine bidirektionale Assoziation.
Außerdem ist für die Umsetzung in ein Programm sehr wichtig, wie viele Objekte der einen Klasse durch die Assoziation jeweils jedem Objekt der anderen Klasse zugeordnet werden. Dies wird durch die sogenannte Multiplizität der Assoziation ausgedrückt. Der einfachste Fall liegt bei einer 1:1-Assoziation vor: Einem Objekt der einen Klasse wird genau ein Objekt der anderen Klasse zugeordnet. In diesem Fall genügt ein einzelnes Attribut zur Aufnahme der Referenz.
Aufgabe 3
Für die Verwaltung einer Spedition werden zwei Klassen angelegt: Eine Klasse LKW, in dessen Konstruktor Marke, Tankinhalt und Verbrauch (in l pro 100 km) initialisiert werden. Die Klasse bekommt Getter-Methoden für Tankinhalt und Verbrauch und eine Setter-Methode für den Tankinhalt. Die Klasse Fahrer initialisiert im Konstruktor Name, Vorname und LKW und die Fahrtstrecke wird auf 0 gesetzt. Weiterhin bekommt die Klasse eine Methode fahren(int strecke), die die gefahrene Strecke speichert und den Tankinhalt aktualisiert. Eine weitere Methode benzinstand() gibt den aktuellen Tankinhalt zurück. Erstelle zuerst ein Klassendiagramm, das die Assoziation wiedergibt.
Hinweise: Zuerst ein LKW-Objekt erzeugen und dieses beim Konstruktor des Fahrers durch einen Mausklick als Parameterwert übernehmen. Der Benzinverbrauch auf einer bestimmten Strecke errechnet sich aus (Strecke*Verbrauch in l) / 100.
Implementierung einer unidirektionalen 1:n-Beziehung
Immer dann, wenn ein Objekt auf mehrere andere Objekte verweisen muss, reicht eine einfache Referenzvariable vom Typ der anderen Seite nicht mehr aus. Dann werden Datenstrukturen benötigt, die mehrere Referenzen aufnehmen können. In Computerspielen kann ein Spieler häufig Gegenstände, Waffen etc. sammeln. Um dies in der Programmierung umzusetzen, muss man auf der 1-Seite eine Datenstruktur verwenden, die eine Anzahl anderer Objekte aufnimmt. Handelt es sich um eine feste Anzahl von Gegenständen, ist als Datenstruktur das bereits bekannte Array gut geeignet. Bei Spielen, in denen sich die Anzahl der Gegenstände dynamisch ändert, ist ein Array wenig elegant, da die manuelle Vergrößerung oder Verkleinerung des Arrays aufwendig ist. Hier kann man beispielsweise die Klasse ArrayList aus dem Package java.util.ArrayList zum Einsatz bringen.
Exkurs:Die Klasse ArrayList
Die Klasse ArrayList dient zum Speichern von Objekten. Die ArrayList, hat gegenüber dem Array den Vorteil, dass sie jederzeit vergrößert oder verkleinert werden kann. Darüber hinaus benötigt die ArrayList als normales Java-Objekt keine besondere Array-Syntax. Allerding kann die ArrayList nur Elemente vom Typ Object speichern, d.h. es ist (zumindest ohne weitere Vorarbeit) ungeeignet um Zahlen abzuspeichern. Für die Zwecke der objektorientierten Programmierung ist sie allerdings ein sehr geeignetes Werkzeug und bietet einige sehr hilfreiche Methoden (siehe Dokumentation). Die wichtigsten sind hier in einem Anwendungsbeispiel demonstriert:
import java.util.ArrayList; public class ArrayListBeispiel { public ArrayListBeispiel() { //anlegen einer ArrayListe mit Objekten vom Typ String ArrayList<String> liste = new ArrayList<String>(); //fuellen der Liste mit Daten liste.add("Keule"); liste.add("Morgenstern"); liste.add("Wattebausch"); liste.add("Schleuder"); liste.add("Armbrust"); //Ausgeben der Liste System.out.println("Alle meine Waffen:"); System.out.println(liste); System.out.println(); //Mit dem Wattebausch kann man keinen schrecken! liste.remove("Wattebausch"); // nochmal ausgeben System.out.println("Liste nach Entfernen des Wattebauschs"); System.out.println(liste); System.out.println(); // Testen ob der Wattebausch noch enthalten ist: if (liste.contains ("Wattebausch")){ System.out.println("Der Wattebausch ist noch drin."); }else{ System.out.println("Der Wattebausch ist draußen."); } // Testen ob Morgenstern enthalten: if (liste.contains ("Morgenstern")){ System.out.println("Der Morgenstern ist noch drin."); }else{ System.out.println("Der Morgenstern ist draußen."); } // Das erste Element der Liste abfragen System.out.println(); System.out.print("Das erste Element der Liste: "); System.out.println(liste.get(0)); // Die Größe der ArrayList abfragen System.out.println(); System.out.print("Laenge der ArrayList: "); System.out.println(liste.size()); System.out.println(); //Auflisten aller Elemente über erweiterte Schleife for (String s : liste) { //gelesen: für alle s vom Typ String in der ArrayList liste System.out.println(s); } } }
Ausgabe:
Alle meine Waffen: [Keule, Morgenstern, Wattebausch, Schleuder, Armbrust] Liste nach Entfernen des Wattebauschs [Keule, Morgenstern, Schleuder, Armbrust] Der Wattebausch ist draußen. Der Morgenstern ist noch drin. Das erste Element der Liste: Keule Laenge der ArrayList: 4 Keule Morgenstern Schleuder Armbrust
Aufgabe 1
Das bereits bekannte Beispiel "Bank" soll nun erweitert werden. Zuerst erhält die Klasse Konto noch zwei Methoden einzahlen() und auszahlen(), mit denen ein bestimmter Betrag ein- bzw. ausgezahlt werden kann. Dann erstellen wir eine Klasse Bank, die eine ArrayList vom Typ Konto enthält. Dazu noch eine Methode zum Hinzufügen von Konten, eine Methode zur Ausgabe sämtlicher Kunden mit den aktuellen Kontoständen und einer Methode, mit der ein Betrag von einem Konto auf ein anderes überwiesen werden kann.
Um die Funktionalität zu überprüfen, erstellt man in BlueJ zuerst einmal drei Konto-Objekte und anschließend ein Bank-Objekt. Ruft man im Bank-Objekt die Methode hinzufuegen(Konto k) auf, dann kann man durch Klicken auf ein Konto-Objekt dieses in die ArrayList übernehmen. Auf diese Weise kann man auch bei der Methode ueberweisen(Konto a, Konto b, int betrag) die Parameterwerte für die Konten übergeben. Anschließend kann man verschiedene Kontobewegungen simulieren und entweder über den Menüeintrag Inspect bei den Konto-Objekten oder über den Aufruf der Daten mit der Ausgabemethode überprüfen.
Aufgabe 2
Ausgangspunkt sei die ebenfalls bekannte Klasse Schueler. Erstelle eine Klasse Schule, die eine ArrayList zum Abspeichern der Schüler enthält. Darüberhinaus enthält die Klasse eine Methode schuelerHinzufuegen(), mit der neue Schüler mit ihren Daten in die ArrayList eingefügt werden können, eine Methode schuelerzahlAusgeben(), die die Anzahl der Schüler in der Liste ausgibt und eine Methode aktuelleListe(), die die in der ArrayList enthaltenen Schueler mit ihren Daten ausgibt.
Aufgabe 3
Der Handyverkäufer Checker D. baut für sein Geschäft eine Kundendatei auf. In einer Klasse Kunde können Kundenobjekte mit den Attributen Name, Vorname, Wohnort und Anzahl (der Handys) erzeugt werden. Es sind für die Attribute nur die entsprechenden Getter-Methoden zu erstellen. Eine weitere Klasse Kundenverwaltung enthält eine ArrayList zur Verwaltung der Kunden. Daneben enthält die Klasse mehrere Methoden. Mit der Methode kundeHinzufuegen() können neue Kunden in die ArrayList eingefügt werden (Es darf angenommen werden, dass die Kundenobjekte bereits erzeugt wurden.). Eine Methode kundenliste() gibt sämtliche Kundendaten in einer Liste aus. Mit wohnortSuche kann nach Eingabe eines Ortes ermittelt werden, wieviele Kunden aus dem entsprechenden Wohnort stammen (Hinweis: Hier ist ein Stringvergleich notwendig). Die Methode gesamtzahl() schließlich stellt fest, wieviele Kundenhandys das Geschäft insgesamt betreut.
Wenn wir nun von einem Spiel ausgehen, in dem ein Spieler Waffen sammeln kann, könnte die Umsetzung mit Hilfe einer ArrayList folgendermaßen aussehen:
public class Weapon { public String bezeichnung; public Weapon( String bezeichnung ) { this.bezeichnung = bezeichnung; } }
Die Klasse Spieler erhält nun ein Attribut weapons von Typ ArrayList:
private ArrayList<Weapon> weapons = new ArrayList<Weapon>();
Damit sieht die Klasse wie folgt aus:
public class Spieler { private ArrayList<Weapon> weapons = new ArrayList<Weapon>(); public void addWeapon( Weapon w ) { weapons.add(w); } public void listWeapons() { for(int i=0;i<weapons.size();i++){ System.out.println( weapons.get(i).bezeichnung );} } }
Zuletzt führen wir Spieler und Waffen in der Klasse Spiel zusammen:
public class Spiel{ public Spiel(){ Spieler uschi = new Spieler(); uschi.addWeapon( new Weapon( "Keule" ) ); uschi.addWeapon( new Weapon( "Morgenstern" ) ); uschi.listWeapons(); // Ausgabe: Keule Morgenstern } }
Klassenvariablen und Klassenmethoden
Bis jetzt kennen wir bei Variablen zwei Typen: Lokale Variablen, wie wir sie beispielsweise in Schleifen als Zählvariablen verwenden, und Instanzvariablen. Innerhalb der Klasse Kreis, die sozusagen den Datentyp Kreis definiert, haben wir beispielsweise Radius, Fuellfarbe oder Linienstärke als Instanzvariablen. Jede Variable wird für sich separat für eine Instanz (also ein aus der Klasse erzeugtes Objekt) angelegt, d.h. jede Instanz hat eine eigene Kopie der Variablen. Die Methoden beziehen sich auf die jeweils angelegte Instanz.
Damit alle Objekte einer Klasse eine bestimmte Variable gemeinsam verwenden können, gibt es die sog. Klassenvariablen. Analog dazu benutzt man Klassenmethoden für Operationen, die sich nicht auf ein bestimmtes Objekt, sondern auf die Klasse an sich beziehen. Variablen und Methoden werden mit dem Schlüsselwort static an die jeweilige Klasse gebunden. Der Zugriff auf statische Variablen erfolgt über Klassenname.Variablenname, der Zugriff auf statische Methoden über Klassenname.Methodennname
Folgendes Beispiel soll das Konzept verdeutlichen:
In einer Klasse Testobjekt wird eine statische Variable zaehler angelegt, in der die Anzahl der erzeugten Instanzen der Klasse abgespeichert wird. Über eine statische Getter-Methode kann der aktuelle Wert des Zählers abgefragt werden. In einer zweiten Klasse ZaehlerTest werden drei Instanzen der Klasse Testobjekt erzeugt und als Kontrolle ihre jeweilige Rangzahl ausgegeben. Am Ende wird über die Methode getZaehler() die Anzahl der erzeugten Instanzen abgefragt.
public class Testobjekt { // Klassenvariable static int zaehler = 0; // Instanzvariable int nummer; // Konstruktor public Testobjekt() { zaehler= zaehler+1; nummer = zaehler; } // Klassenmethode static int getZaehler() { return zaehler; } } public class Zaehlertest{ public void zaehlerTest(){ System.out.println("Klassenvariable zaehler:"+ Testobjekt.getZaehler()); Testobjekt obj_1 = new Testobjekt(); Testobjekt obj_2 = new Testobjekt(); Testobjekt obj_3 = new Testobjekt(); System.out.println("Instanzvariable nummer im 1. Objekt: "+ obj_1.nummer); System.out.println("Instanzvariable nummer im 2. Objekt: "+ obj_2.nummer); System.out.println("Instanzvariable nummer im 3. Objekt: "+ obj_3.nummer); System.out.println("Klassenvariable zaehler: " + Testobjekt.getZaehler()); } }
Aufgabe 1
Erstelle wiederum ausgehend von der bekannten Klasse Schueler eine Klasse Schueler, in der die Zahl der erzeugten Instanzen festgehalten und über eine Getter-Methode ausgelesen werden kann. Schreibe nun eine zweite Klasse Schule in deren Konstruktor vier Schueler-Objekte erzeugt werden und durch den Aufruf der Getter-Methode die Anzahl der erzeugten Instanzen der Klasse Schueler angezeigt wird.
Aufgabe 2
Löse das obige Problem mit Hilfe eines Arrays in der Klasse Schule. Entferne die Ausgabe-Methode aus der Klasse Schueler und verlege die Ausgabe in eine Methode schuelerAusgeben() in der Klasse Schule. Zusätzlich soll eine Methode klassenstaerke() erstellt werden, die nach Eingabe der gewünschten Klasse deren Stärke auf der Konsole ausgibt. Hinweis: Um einen Zugriff auf die Attribute der Schueler-Objekte zu erlauben, sind Getter-Methoden in der Klasse Schueler notwendig.
Aufgabe 3
Erstelle eine Klasse Kasse mit einem Konstruktor, der nur den Namen der Kasse und den Betrag für die Einnahmen enthält. Mit Hilfe einer Klassenvariable einnahmen sollen die Gesamteinnahmen der erzeugten Kassen-Objekte festgehalten werden. Eine entsprechende Getter-Methode erlaubt den Zugriff auf den Wert der Klassenvariable. Eine weitere Klasse Abrechnung enthält nur eine Methode gesamteinnahmen(), die unter Rückgriff auf die Klassenvariable von Kasse die Gesamteinnahmen der erzeugten Kassen-Objekte auf der Konsole ausgibt.
Aufgabe 4
Für eine Firma soll ein Programm zur Abwicklung ihrer Aufträge erstellt werden. Die Grundlage ist folgendes Klassendiagramm:
Ziel ist die Ausgabe einer Bestellbestätigung folgender Art:
Sehr geehrter Herr Huber, wir bestätigen ihre Bestellung von 5 St. Dachziegel zum Stückpreis von 3,59 Euro. Der für Sie zuständige Sachbearbeiter ist Heribert Freundlich. Ihre Auftragsnummer: 1/2015.
Hinweis: Um eine Instanz der Klasse Auftrag zu erzeugen, müssen die zugehörigen Instanzen der Klassen Kunde, Ware und Mitarbeiter erzeugt werden.