BlueJ
BlueJ ist eine Java-Entwicklungsumgebung, die die Einführung in die objektorientierte Programmierung sehr stark unterstützt. Für den schulischen Einsatz habe ich einen kleinen Text geschrieben, der auch unter http://www.scribd.com/doc/9276095/Objekte-Mit-BlueJ2008 in einer älteren Version zur Verfügung steht. Die benutzten Beispielprojekte stehen unter http://www.debacher.de/downloads/BlueJ-Projekte.tgz zur Verfügung.
1. Ein Grafiksystem in Java
Zur Thematik
Inhaltliches Ziel des aktuellen Semesters ist die Erstellung eines Grafiksystems in Java. Gedacht ist an ein vektororientiertes Grafiksystem, ein zweidimensionales Wohnungseinrichtungssystem für die Kunden eines Möbelhauses. Eine sehr umfangreiche und mächtige 3D-Version eines solchen Programmes steht unter http://www.sweethome3d.com frei zur Verfügung. Auch dieses Programm ist in Java geschrieben.
Das Projekt eignet sich gut, um in Verfahren der objektorientierten Modellierung und Programmierung einzuführen. Als Benutzungsoberfläche wird die Entwicklungsumgebung BlueJ genutzt, die explizit für die Lehre entwickelt wurde und kostenfrei verfügbar ist (http://www.bluej.org).
Mit dieser Entwicklungsumgebung kannst du zunächst mit einem stark reduzierten, vereinfachten Modell interagieren und damit die notwendigen Grundvorstellungen von Objekten, Attributen, Methoden, Nachrichten und Klassen aufbauen.
Anschließend setzt du den Programmquellcode in Beziehung zum Verhalten der Objekte und erweitern ihn, um das System schrittweise auszubauen. Dies geschieht in mehreren Schritten:
- interaktiv Gruppen von Möbeln erstellen
- Klassen für Möbelgruppen erstellen, Aggregation (Teile-Ganzes-Beziehung) thematisieren; Beispiele: Schrankwand zunächst interaktiv erstellen, anschließend Klasse Schrankwand mit n Schränken. Ebenso z.B. Sitzgruppe, Doppelbett, Computerarbeitsplatz, Computerraum.
- bestehende Klassen ausbauen
- neue Möbelklassen hinzufügen
Vektorformat vs. Pixelformat
Im Computerbereich sind zwei Arten von Anwendungen bzw. Dateiformaten gebräuchlich.
- Einerseits Malprogramme wie MS-Paint, bei denen alle Abbildungen aus einzelnen Punkten zusammengesetzt sind. Man kann in der Bedienoberfläche dieser Programme zwar Linien, Rechtecke oder Kreise zum Zeichnen auswählen, sowie die Objekte aber dargestellt sind kann die Anwendung diese nicht mehr als solche verschieben, da nur Menge von Punkten gezeichnet wird. Der Bildschirmpunkt ist das einzige Objekt, welches diese Anwendungen wirklich kennen. Nachträglich ist es auch nicht mehr möglich, die Farbe oder die Breite einer Linie zu verändern.Beim Vergrößern eines derartig gemalten Bildes werden schräge oder runde Linien zu recht stufigen Gebilden.
- Deutlich flexibler sind die Zeichenprogramme, wie z.B. OpenOffice Draw. Derartige Programme besitzen intern eine Beschreibung für Objekte wie Linien, Kreise und Rechtecke. Du kannst daher die Objekte jederzeit erneut auswählen und jede ihrer Eigenschaften verändern.Auch beim Vergrößern eines Ausschnittes bleiben alle Linien glatt und gleichmäßig, da die Darstellung des Objektes der neuen Auflösung angepasst wird. Da auch die Druckqualität entsprechend hoch ist, arbeiten alle Konstruktionsprogramme objektorientiert.
Normzeichen für das Zeichnen von Möbeln
Für das Zeichnen von Möbeln gibt es genormte Darstellungen, die auch für das vorliegende Projekt zur Anwendung kommen sollen.
2. Klassen und Objekte
Bevor eine Grafikanwendung wie das beschriebene Möbelsystem realisierbar ist, müssen erst einmal ein Paar Grundlagen geklärt werden, vor allem die Begriffe Klasse und Objekt.
Einführung
Zur Einführung kannst du dir die Elemente eines Computer-Desktops ansehen: Buttons auf der Taskleiste, Programmlinks, Dokumente, usw.
Fragestellungen:
- Was seht ihr?
- Welche Gemeinsamkeiten haben die einzelnen Elemente auf dem Desktop?
- Welche Eigenschaften haben diese Elemente?
- Was kann ich mit diesen Elementen machen?
Ergebnisse:
- Es gibt Gruppen gleichartiger Elemente, z.B. Buttons, Dokumente, Programme und Links.
- Die Gruppen werden als Klassen bezeichnet.
- Die einzelnen Elemente sind die Objekte der jeweiligen Klasse.
- Die Elemente bestehen u.a. aus Icon und Namen (Attribute).
- Sie reagieren unterschiedlich (Programme werden gestartet, Dokumente geöffnet), man kann sie verschieben usw. (Methoden).
Weitere mögliche Beispiele sind:
- Mediator
- Textverarbeitung (Absätze, Zeichen)
- Präsentationsprogramm
- eigentlich jedes Programm mit einer grafischen Benutzeroberfläche.
Aufgabe: Nenne Beispiele für Klassifizierungen aus dem täglichen Leben!
Beispiele:
- Körperformen (s. Arbeitsblatt Körperformen)
- Tiere, Pflanzen, Menschen,
- Auto und ein konkretes Auto zur Unterscheidung zwischen Klasse und Exemplar (Instanz).
Der natürliche Umgang mit Dingen/Objekten
Natürlicherweise sind wir den Umgang mit Dingen gewohnt. Wir können Dinge sehen oder fühlen, aber Dinge können auch nur in der Vorstellung bestehen. Über die Dinge des Lebens ließe sich weiter philosophieren, aber das muss an anderer Stelle geschehen.
Im Umgang mit den Dingen haben wir gelernt:
- Dinge besitzen Eigenschaften,
- Dinge können sich verändern,
- Veränderungen haben eine Ursache, wenn wir sie auch nicht in jedem Fall verstehen.
Wir können abstrahieren. So haben wir beispielsweise erkannt:
- Dinge können aus Teilen bestehen
- Dinge können kategorisiert werden.
Genau dies spielt auch im Zusammenhang mit Klassen und Objekten eine Rolle.
Modellbildung
Programmierung ist ein Modellbildungsprozess. Bei allen Modellbildungen betrachtet man einen Ausschnitt der realen Welt und versucht ihn mit einem formalen System, z.B. einer Computersprache zu beschreiben.
Wesentlicher Teil des Modellbildungsprozesses ist es diejenigen Objekte zu identifizieren, die auch im Modell eine Rolle spielen. Im weiteren Verlauf der Modellbildung geht es dann darum die identifizierten Objekte zu kategorisieren und in Klassen zusammen zu fassen.
Klasse
Klassen fassen Objekte mit gleichen Eigenschaften und gleichem Verhalten zusammen. Schreiben wir in Java eine Klassendefinition, dann beschreiben wir in ihr, welche Eigenschaften und welches Verhalten ein Objekt hätte, das dieser Klasse zugehören soll, wenn wir es erzeugen würden.
Objekt
Ein Objekt ist ein konkretes Exemplar, eine Instanz, die zu einer Klasse gehört.
Deutlich werden die Begriffe, wenn man auf alltägliche Beispiele abhebt. Relativ wichtig in unserem Alltag ist die Klasse Automobil, die bestimmte Fahrzeuge zusammenfasst.
Der alte Bully des Deutschlehrers ist dann ein konkretes Objekt, eine Instanz der Klasse Auto, genau, wie der Mercedes des Physiklehrers ein Objekt darstellt.
Im Zusammenhang mit der Textverarbeitung lassen sich dann Klassen wie Absatz, Zeichen oder Seite identifizieren. Eine konkrete Instanz der Klasse Absatz und damit ein Objekt der Textverarbeitung wäre dann der vorliegende Textteil.
3. Objekte und Klassen mit BlueJ
Arbeiten mit der Oberfläche von BlueJ
Die Arbeit mit BlueJ erlaubt einen einfachen Umgang mit Klassen und Objekten in der Programmiersprache Java. Beim ersten Start sieht die Oberfläche von BlueJ sehr aufgeräumt aus.
Für die ersten Schritte benötigst du die Menüleiste, in der sich der Punkt Projekt findet. In dem Projekt-Menü findet du den Unterpunkt Projekt öffnen... über den du einen Dialog zum Öffnen von BlueJ-Projekten erhält. Im Verzeichnis Abschnitt 3 der Materialien zu dieser Einführung findet sich u.a. das BlueJ-Projekt Figuren. Nach dem Laden dieses Projektes ergibt sich das folgende Bild.
Im Hauptfenster von BlueJ ist ein Diagramm zu sehen, das die Klassen des Projektes Figuren und ihren Zusammenhang darstellt. Konkret sind dies die Klassen
- Dreieck,
- Quadrat,
- Kreis und
- Leinwand.
Die Pfeile machen deutlich, dass die drei geometrischen Klassen die Klasse Leinwand benutzen.
Klicke jetzt mit der rechten Maustaste auf die Klasse Kreis und wähle aus dem Kontextmenü den ersten Menüpunkt new Kreis() aus. Im anschließenden Dialog, der dich nach einem Namen für die Instanz von Kreis fragt, kannst du einfach auf OK klicken und damit die Vorgabe kreis1 akzeptieren. Es sollte sich jetzt das folgende Bild ergeben.
In der Objektleiste von BlueJ ist jetzt die Instanz kreis1 der Klasse Kreis abgelegt. Vermutlich erfüllt diese Darstellung noch nicht deine Erwartungen, da kein Kreis auf dem Bildschirm zu sehen ist.
Methoden aufrufen
Um den zugehörigen Kreis auf den Bildschirm zu bekommen, klickst du mit der rechten Maustaste auf das Objekt in der Objektleiste, worauf sich sein Kontextmenü öffnet.
Hier findest du alle Methoden, die die Klasse Kreis zur Verfügung stellt. Wenn du die Methode sichtbarMachen aufrufst, dann erscheint auch ein Kreis auf dem Bildschirm und zwar in einem speziellen Fenster, welches zur Klasse Leinwand gehört.
Aus dem Kontextmenü von kreis1 heraus kannst du jetzt weitere Methoden aufrufen und damit z.B. den Kreis horizontal bzw. vertikal verschieben. Manche dieser Methoden, z.B. horizontalBewegen erwarten eine Eingabe.
Hier gibst du eine Zahl ein z.B. 50, worauf der kreis1 sich um 50 Pixel horizontal verschiebt.
Etwas aufpassen musst du, wenn du die Methode farbeAendern aufrufst, hier erwartet der kreis1 keine Zahl, sondern einen Text, der die Farbe angibt.
Dieser Text muss in Anführungsstriche gesetzt werden und es stehen auch nur die im Dialogfenster angegebenen Farben zur Verfügung.
Eine Methode kann mehr als nur einen Parameter besitzen.
Die Methode groesseAendern des Klasse Dreieck z.B. erwartet die Angabe von zwei Parametern, nämlich Höhe und Breite.
Datentypen in Java
Ein Datentyp beschreibt die Art der Information, die Java z.B. als Parameter einer Methode erwartet. Viele der Methoden von Kreis erwarten Zahlen als Parameter, der zugehörige Datentyp heißt in Java int, was eine Abkürzung von integer, dem englischen Begriff für ganze Zahl ist.
Die Methode farbeAendern erwartet einen Text, eine Zeichenkette. Der zugehörige Datentyp heißt in Java String. Strings oder Zeichenketten musst du generell in Anführungsstriche setzen.
Mehrere Instanzen
Bisher hast du nur mit einem einzigen Objekt gearbeitet, einer Instanz der Klasse Kreis. Du kannst beliebig viele Instanzen der gleichen Klasse erzeugen, aber auch Instanzen verschiedener Klassen gleichzeitig. Bei verschiedenen Instanzen der gleichen Klasse musst du aber unbedingt darauf achten, dass sie unterschiedliche Positionen besitzen, sonst kannst du sie im Leinwandfenster nicht unterscheiden.
Zu diesem Projekt könnte z.B. die folgende Leinwand passen.
Aufgabe 1:
a) Erzeuge mit BlueJ und den Klassen Kreis, Quadrat und Dreieck interaktiv eine Abbildung, die ein einfaches Haus mit einer Sonne darstellt.
b) Versuche dabei dir die einzelnen Schritte bzw. Methodenaufrufe so zu merken, dass du eine erneute Darstellung des gleichen Bildes möglichst direkt erzeugen könntest.
c) Falls du genügend Zeit zur Verfügung hast, kannst du die Zeichnung noch um weitere Elemente, wie z.B. Tannenbäume ergänzen.
Zustand von Objekten
Bei der Arbeit mit mehreren Objekten, vor allem bei der exakten Positionierung, kann es immer wieder vorkommen, dass du Angaben über einzelne Objekte benötigst, z.B. ihre Größe oder ihre Position. Die Werte wie Position, Farbe, Durchmesser, die ein Objekt zu einem Zeitpunkt besitzt, bezeichnet man als seinen Zustand.
Der Zustand eines Objektes lässt sich mit BlueJ relativ einfach ermitteln bzw. inspizieren. Im Kontextmenü jedes Objektes steht dir der Menüpunkt Inspizieren zur Verfügung.
Der Zustand eines Objektes der Klasse Kreis lässt sich durch die angegebenen fünf Datenfelder beschreiben, die hier für die Instanz kreis1 zu sehen sind. Jede Instanz der Klasse Kreis verfügt über die gleichen Datenfelder, aber die Werte unterscheiden sich normalerweise von Instanz zu Instanz. Andererseits können sich aber zwei Instanzen der Klasse Kreis auch nur in diesen Datenfeldern unterscheiden.
Klassen können Klassen benutzen
Ein Problem im Zusammenhang mit der Aufgabe 1 besteht darin, dass sich das Ergebnis nicht speichern lässt, die einzelnen Methodenaufrufe erfolgen interaktiv. Aber auch dieses Problem lässt sich relativ einfach lösen. Schließe das Projekt Figuren (Projekt -> Schließen) und öffne das Projekt Zeichnung (Projekt -> Projekt öffnen... -> Zeichnung), das sich im gleichen Verzeichnis befinden sollte. Hier findest du eine neue Klasse Zeichnung, die die Klassen Kreis, Quadrat und Dreieck benutzt, wie die Pfeile symbolisieren.
Erzeuge die eine Instanz der Klasse Zeichnung (rechte Maustaste -> new Zeichnung()), wobei der Name der Instanz keine Rolle spielt, du also die Vorgabe zeichnung1 übernehmen kannst. Von dem soeben erzeugten Objekt ist erst einmal nichts zu sehen, da es sich nicht automatisch zeichnet. Klicke mit der rechten Maustaste auf das Objekt in der Objektleiste und rufe die Methode zeichne auf. Es ergibt sich das gleiche Bild, das du bei Aufgabe 1 interaktiv erzeugen solltest. Die Schritte, die du zur Lösung von Aufgabe 1 durchgeführt hast, müssen sich in der Klasse Zeichnung wiederfinden.
Rufe jetzt das Kontextmenü der Klasse Zeichnung auf, nicht des Objektes zeichnung1 und wähle dort den Menüpunkt Bearbeiten, dann öffnet sich ein Editor mit dem Quelltext der Klasse Zeichnung. Sollten bei dir keine Zeilennummern angezeigt werden, so kannst du dies im Menü des Editors unter Optionen -> Einstellungen -> Zeilennummern anzeigen ändern.
Aufgabe 2
Verändere den Quelltext von Zeichnung so, dass die Sonne nicht mehr gelb, sondern rot ist. Dazu musst du im Quelltext die Zeile suchen, in der die Farbe der Sonne geändert wird.
Aufgabe 3
a) Erweitere den Quelltext von Zeichnung um ein weiteres Objekt, einen Mond. Der Mond soll etwas kleiner sein als die Sonne und links vom Dach erscheinen.
b) Die Klasse Zeichnung kennt die Methoden inSchwarzWeissAendern und inFarbeAendern. Achte darauf, dass diese Methoden auch den Mond beeinflussen.
c) Falls du noch Zeit hast kannst du deine Zeichnung um einen Tannenbaum erweitern, der unterhalb der Sonne neben dem Haus stehen kann.
Aufgabe 4 (anspruchsvoller)
Der Mond aus Aufgabe 3 soll nach dem Zeichnen langsam untergehen.
a) Die Klasse Kreis stellt die Methoden langsamHorizontalBewegen und langsamVertikalBewegen zur Verfügung, die du innerhalb von zeichne benutzen kannst.
b) Schreibe eine extra Methode monduntergang, die von der Methode zeichne getrennt ist. Man soll also mittels zeichne das Bild aufbauen und dann mit monduntergang den Mond untergehen lassen.
4. Beziehungen zwischen Klassen
Bisher ging es hauptsächlich um den Unterschied zwischen Klassen und Objekten. Im vorliegenden Abschnitt wird es hauptsächlich um die Beziehungen zwischen einzelnen Klassen gehen. Hier werden dann so wichtige Begriffe wie Vererbung und Aggregation.
Öffne zum Einstieg das Projekt MoebelAnfang aus dem Verzeichnis Abschnitt4. Das Projekt ähnelt dem Einstiegsbeispiel Figuren, von daher dürfte der Einarbeitungsaufwand gering sein. Das Projekt beinhaltet wieder eine Leinwand-Klasse und die Klassen Stuhl und Tisch.
Sowohl Tisch, als auch Stuhl verfügen über eine Methode dreheAuf, über die die Orientierung der Möbelstücke verändert werden kann. Der Winkel wird hierbei im Gradmass angegeben.
In der folgenden Aufgabe sollst du eine eigene Klasse erstellen. Dazu sind ein paar Informationen zu Java-Klassen notwendig. Bei einer Java-Klasse müssen Dateiname und Klassenname übereinstimmen. Eine Klasse Einrichtung muss also unter dem Dateinamen Einrichtung.java abgespeichert sein, wobei auch die Gross-/Kleinschreibung wichtig ist. Jede Java-Klasse verfügt über einen Konstruktor, der dazu dient Voreinstellungen vorzunehmen. Der Konstruktor darf ruhig leer sein, muss aber so heissen, wie die Klasse.
public Einrichtung() { // nichts zu tun hier, alle Exemplarvariablen werden automatisch // mit null initialisiert }
Java führt bei leerem Konstruktor automatisch einige Initialisierungen durch. Oftmals wird man aber im Konstruktor auch eigene Einstellungen vornehmen:
public Tisch() { xPosition = 120; yPosition = 150; orientierung = 0; farbe = "rot"; istSichtbar = false; breite = 120; tiefe = 100; }
Einem Konstruktor kann man auch Parameter übergeben, wie einer normalen Methode. Das wird in einem späteren Abschnitt eine Rolle spielen.
Aufgabe5
Erstelle mit dem Projekt MoebelAnfang eine kleine Sitzgruppe, zuerst interaktiv und dann, indem du eine zusätzliche Klasse Einrichtung einführst, die die Sitzgruppe darstellt. Du kannst dich bei der Erstellung an der Klasse Zeichnung aus Abschnitt 3 orientieren.
Aggregation und Komposition
Bei der Objektorientierung Modellierung ist es wichtig sich deutlich zu machen, in welcher Beziehung die verwendeten Klassen zueinander stehen. Das Verhältnis zwischen Einrichtung und Tisch bzw. Stuhl ist eine hat-Beziehung, die man in diesem Fall als Aggregation bezeichnet. Die Einrichtung hat einen Tisch und die Einrichtung hat Stühle. Man könnte auch sagen, die Einrichtung besteht aus Tisch und Stühlen.
Für die Verwendung des Begriffes Aggregation ist es wichtig, dass die benutzten Klassen auch unabhängig von der benutzenden Klasse existenzfähig sind. Falls diese Unabhängigkeit nicht gegeben ist spricht man von einer Komposition. Ein Tisch hat Tischbeine, diese würde man normalerweise nicht als vom Tisch unabhängig existenzfähig betrachten. Wenn ich den Tisch wegwerfe, dann normalerweise auch seine Beine.
Ein weiteres Merkmal der Aggregation ist auch dass das Aggregat auch zeitweise ohne Teile sein darf, eine Einrichtung ohne Tisch und Stuhl ist denkbar, ein Tisch ohne Beine aber nicht.
Aggregation und Komposition gehören beide zu den Assoziationen. BlueJ stellt Assoziationen durch Pfeile zwischen den Klassen dar, unterscheidet aber nicht zwischen Aggregation und Komposition.
Weitere Möbelklassen
Eine Einrichtung, die nur aus Tischen und Stühlen besteht ist relativ langweilig, deshalb soll das Programm im nächsten Schritt um weitere Möbelklassen erweitert werden.
Aufgabe 6
Schreibe die Möbelklassen Bett, Sofa und Schrankelement, orientiere dich bei der Darstellung an den Normzeichen für Möbel von Abschnitt 1.
Grafikbefehle in Java
Die Lösung der Aufgabe 6 ist solange nicht besonders schwierig, wie man bei Objekten bleibt, die sich aus Linienzügen zeichnen lassen. Die Möbelstücke unterscheiden sich vor allem in der Methode gibAktuelleFigur(). Für den Stuhl hat die Methode folgenden Inhalt:
private Shape gibAktuelleFigur() { GeneralPath stuhl = new GeneralPath(); stuhl.moveTo(xPosition , yPosition); stuhl.lineTo(xPosition+breite, yPosition); stuhl.lineTo(xPosition+breite+3, yPosition+tiefe); stuhl.lineTo(xPosition-3 , yPosition+tiefe); stuhl.lineTo(xPosition , yPosition ); //Das ist die Umrandung. Das Stuhl bekommt noch eine Lehne stuhl.moveTo(xPosition , yPosition + 5 ); stuhl.lineTo(xPosition + breite , yPosition + 5); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); return t1.createTransformedShape(stuhl); }
Zentrale Komponente hier ist GeneralPath, was man als Zeichenanweisung interpretieren kann. Hier werden quasi die Anweisungen bzw. Arbeitsschritte gespeichert, die man zum Zeichen des Objektes benötigt. Benutzt werden hier zum Zeichnen die Methoden moveTo() und lineTo(). Die erste Methode verschiebt die Position des Zeichenstiftes ohne eine Spur zu hinterlassen. Die zweite Methode hinterlässt eine Spur.
Die letzten drei Zeilen der Methode definieren eine Transformation, konkret eine Drehung der Zeichnung um ihren Mittelpunkt.
Die Methode gibAktuelleFigur der Klasse Tisch besitzt einen kleinen Designfehler. Schöner wäre folgende Version:
private Shape gibAktuelleFigur() { GeneralPath tisch = new GeneralPath(); tisch.append(new Ellipse2D.Double(xPosition , yPosition, breite, tiefe), false); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); return t1.createTransformedShape(tisch); }
Hier wird eine Instanz der Klasse Ellipse (ein Tisch) an einen Zeichenpfad angehängt. Über diesen Weg kann man den Zeichenpfad jederzeit erweitern. Das false am Ende der Append-Zeile bewirkt übrigens, dass der Tisch nicht mit dem vorherigen Objekt verbunden wird.
Mit diesen Informationen sollten sich Möbelstücke wie Bett, Schrank und Sofa zeichnen lassen. Etwas schwieriger wird es, wenn man abgerundete Möbelstücke, wie Klavier oder Badewanne zeichnen möchte. Hier benötigt man weitere Informationen.
Java ist eine sehr gut dokumentierte Sprache, die Dokumentation wird automatisch aus Kommentaren im Quelltext erzeugt werden.
In BlueJ findet sich unter Hilfe der Menüpunkt Java Klassenbibliotheken, über den man einen Web-Browser mit der Dokumentation von Sun aufrufen kann.
Welche Webseiten hierbei geladen werden kann man ggf. unter Werkzeuge ● Einstellungen ● Diverses festlegen. Aktuell ist das die Adresse: http://download.oracle.com/javase/1.5.0/docs/api/
Die Seite besteht aus drei Frames.
Java besteht aus mehreren Paketen, z.B. dem Paket java.awt.geom mit Grafikfunktionen. Klick man im Frame links oben auf java.awt.geom, so werden im Frame direkt darunter alle Klassen angezeigt. Klickt man hier z.B. auf die Klasse GeneralPath, so erscheint im großen Frame die zugehörige Dokumentation mit allen Methoden dieser Klasse.
Hier finden sich dann z.B. auch moveTo() und LineTo() mit relativ abstrakten Beschreibungen. Recht mächtig sind hier auch die Methoden quadTo() und curveTo(), die die Angabe von zwei bzw. drei Punkten erwarten. Die Methode quadTo() verbindet den aktuellen Punkt mit dem zweiten Punkt über eine Kurve, deren Krümmung mit dem ersten angegebenen Punkt bestimmt wird.
Das folgende Listing erzeugt einen Kreisbogen, um den herum ein Quadrat gezeichnet ist.
private Shape gibAktuelleFigur() { GeneralPath Test = new GeneralPath(); Test.moveTo(xPosition , yPosition); Test.lineTo(xPosition+breite, yPosition); Test.lineTo(xPosition+breite, yPosition+tiefe); Test.lineTo(xPosition , yPosition+tiefe); Test.lineTo(xPosition , yPosition); Test.quadTo(xPosition, yPosition+tiefe, xPosition+breite, yPosition+tiefe); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); return t1.createTransformedShape(Test); }
Die Methode curveTo() arbeitet entsprechend, nur dass hier mit zwei Hilfspunkten gearbeitet wird, die die Darstellung von S-förmigen Kurven erlauben.
*** hier muss noch etwas zu append(shape..) folgen.
Ich der Tisch überhaupt sinnvoll konstruiert?
Wäre es nicht besser ihn per append an einen GeneralPath zu hängen??
****
Vererbung
Beim Bearbeiten der Aufgabe 6 müsste dir aufgefallen sein, dass du mehrfach gleiche bzw. sehr ähnliche Codezeilen schreiben musst. Derartige Redundanzen sollte man in der Programmierung unbedingt vermeiden. Jede deiner Möbelklassen verfügt z.B. über die Methode zeige:
public void zeige() { istSichtbar = true; zeichne(); }
Diese Methode ist vollkommen unabhängig von der konkreten Möbelklasse.
Eine Möglichkeit die Redundanzen zu vermeiden besteht darin eine sehr allgemeine Klasse Moebelstueck zu definieren, von der die konkreten Möbelstücke dann erben.
import java.awt.Shape; /** * Ein allgemeines Möbelstück, als Grundlage für Vererbung. * * @author Java-MS Groupies * hier Uwe Debacher * nach Vorlagen von Michael Kölling und David J. Barnes und Axel Schmolitzky * @version 1.1 (23.2.04) */ abstract public class Moebelstueck { public int xPosition; public int yPosition; public int orientierung; public String farbe; public boolean istSichtbar; /** * Erzeuge ein Moebelstueck mit einer Standardfarbe an einer * Standardposition. */ public Moebelstueck() { xPosition = 160; yPosition = 80; farbe = "blau"; orientierung = 0; istSichtbar = false; } /** * Berechnet das zu zeichnende Shape anhand der gegebenen Daten * [ Zum Anzeigen der Attributwerte über Inspect muss hier die Sichtbarkeit * auf public gesetzt werden. ] */ abstract public Shape gibAktuelleFigur(); /** * Mache dieses Objekt sichtbar. Wenn es bereits sichtbar ist, tue * nichts. */ public void zeige() { istSichtbar = true; zeichne(); } /** * Mache dieses Objekt unsichtbar. Wenn es bereits unsichtbar ist, tue * nichts. */ public void verberge() { loesche(); istSichtbar = false; } /** * Drehe auf den angegebenen Winkel */ public void dreheAuf(int neuerWinkel) { loesche(); orientierung = neuerWinkel; zeichne(); } /** * Bewege dieses Objekt horizontal um 'entfernung' Bildschirmpunkte. */ public void bewegeHorizontal(int entfernung) { loesche(); xPosition += entfernung; zeichne(); } /** * Bewege dieses objekt vertikal um 'entfernung' Bildschirmpunkte. */ public void bewegeVertikal(int entfernung) { loesche(); yPosition += entfernung; zeichne(); } /** * Ändere die Farbe dieses Objektes in 'neueFarbe'. * Gültige Angaben sind "rot", "gelb", "blau", "gruen", * "lila" und "schwarz". */ public void aendereFarbe(String neueFarbe) { farbe = neueFarbe; zeichne(); } /* * Zeichne dieses Objekt mit seinen aktuellen Werten auf den Bildschirm. */ private void zeichne() { if (istSichtbar) { Shape figur = gibAktuelleFigur(); Leinwand leinwand = Leinwand.gibLeinwand(); leinwand.zeichne ( this, farbe, figur); leinwand.warte(10); } } /* * Lösche dieses Objekt vom Bildschirm. */ private void loesche() { if (istSichtbar) { Leinwand leinwand = Leinwand.gibLeinwand(); leinwand.entferne(this); } } }
Von dieser Klasse erben dann die konkreten Möbelklassen, was zu deutlich übersichtlicheren Quelltexten führt.
import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.Graphics2D; /** * Ein Tisch, der manipuliert werden kann und sich selbst auf einer Leinwand zeichnet. * * @author Java-MS Groupies * hier claus albowski * nach einer Vorlage von Uwe Debacher, * Michael Kölling und David J. Barnes und Axel Schmolitzky * @version 1.1 (8.2.04) */ public class Tisch extends Moebelstueck { private int breite; private int tiefe; /** * Erzeuge einen neuen Tisch mit einer Standardfarbe an einer * Standardposition. */ public Tisch() { breite = 120; tiefe = 100; } /** * Berechnet das zu zeichnende Shape anhand der gegebenen Daten * [ Zum Anzeigen der Attributwerte über Inspect muss hier die Sichtbarkeit * auf public gesetzt werden. ] */ public Shape gibAktuelleFigur() { Shape tisch = new Ellipse2D.Double(xPosition , yPosition, breite, tiefe); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); return t1.createTransformedShape(tisch); } /** * Hole die X-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteX() { return xPosition+breite/2; } /** * Hole die Y-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteY() { return yPosition+tiefe/2; } }
5. Sammlungen von Objekten
An vielen Stellen werden Sammlungen von gleichartigen Objekten benötigt. Eine Möglichkeit wäre z.B. die Einführung eines Objektes Sitzgruppe, welches selber aus einem Tisch und vier Stühlen bestehen könnte, wie in Aufgabe 5.
Idealerweise möchte man eine derartige Sammlung zu Laufzeit des Programms erstellen und erweitern können, was bei dem bisherigen Ansatz nicht möglich ist.
Java bietet für derartige Situationen mehrere Lösungsmöglichkeiten an.
Aufgabe 7
Öffne das Projekt Notizbuch1 aus dem Verzeichnis Abschnitt5. Das Projekt besteht nur aus einer einzigen Klasse Notizbuch, die einzelne Einträge verwalten kann. Arbeite mit dieser Klasse gib u.a. mehrere Notizen ein.
Analysiere die Funktionsweise der Sammlung, die Notizbuch benutzt.
import java.util.ArrayList; /** * Eine Klasse zur Verwaltung von beliebig langen Notizlisten. * Notizen sind nummeriert, um durch einen Benutzer referenziert * werden zu können. * In dieser Version starten die Notiznummern bei 0. * * @author David J. Barnes and Michael Kolling. * @version 2003.04.15 */ public class Notizbuch { // Speicher für eine beliebige Anzahl an Notizen. private ArrayList notizen; /** * Führe die Initialisierungen durch, die für ein Notizbuch * notwendig sind. */ public Notizbuch() { notizen = new ArrayList(); } /** * Speichere eine neue Notiz in diesem Notizbuch. * @param notiz die zu speichernde Notiz. */ public void speichereNotiz(String notiz) { notizen.add(notiz); } /** * @return die Anzahl der Notizen in diesem Notizbuch. */ public int anzahlNotizen() { return notizen.size(); } /** * Zeige eine Notiz. * @param notiznummer die Nummer der Notiz, die gezeigt werden soll. */ public void zeigeNotiz(int notiznummer) { if(notiznummer < 0) { // Keine gültige Nummer, nichts zu tun. } else if(notiznummer < anzahlNotizen()) { // Die Nummer ist gültig, wir können die Notiz ausgeben. System.out.println(notizen.get(notiznummer)); } else { // Keine gültige Nummer, nichts zu tun. } } }
Aufgabe 8
Erweitere das Projekt Notizbuch so, dass einzelne Notizen auch gelöscht werden können, dafür steht in einer ArrayList die Methode remove(int nummer) zur Verfügung.
Iteration
Will man alle Notizen des Notizbuches ausgeben, so benötigt man eine Wiederholstruktur oder Schleifenstruktur. Java kennt dafür mehrere Konstrukte, die hier jeweils gleich am konkreten Beispiel dargestellt werden.
Die while-Schleife
Sehr einfach zu handhaben ist die while-Schleife. Hier wird ein Programmteil solange ausgeführt, wie eine Bedingung wahr ist.
while (Schleifenbedingung) { Schleifenrumpf }
Konkret bezogen auf das Notizbuch ergäbe sich folgendes Listing.
int index = 0; while(index < notizen.size()) { System.out.println(notizen.get(index)); index++; }
Iteratoren
Bei der Ausgabe der Notizen spielen die Nummern der Einträge eigentlich keine grosse Rolle. Es gibt Sammlungen, bei denen Nummern auch gar nicht vorhanden sind. Da ist ein Konstrukt übersichtlicher, mit dem man formulieren kann, wenn es einen weiteren Datensatz gibt, dann gib den nächsten Datensatz aus. Genau so arbeitet ein Iterator.
Jede Sammlung, wie in diesem Fall die ArrayList, verfügt über ein Iterator-Objekt. Dieses Iterator-Objekt kennt dann die Methoden hasNext() und next().
Iterator it = notiz.iterator(); while(it.hasNext()) { System.out.printeln(it.next()), }
Die for-Schleife
Wenn man genau weiss, wie oft eine Iteration durchzuführen ist, dann kann man auch mit der for-Schleife arbeiten, die schon in einem der ersten Beispiele auftauchte.
for(Initialisierung; Bedingung; Aktion nach dem Rumpf) { Schleifenrumpf }
Bezogen auf das Notizbuch sieht das folgendermassen aus:
for (int index; index<notizen.size(); index=index+1) System.out.printeln(notizen.get(index)); }
Welche der beiden Schleifenarten man bevorzugt hängt von der Situation und auch dem persönlichen Geschmack ab. Im Zweifelsfall sollte man der while-Schleife den Vorzug geben.
Ein Hinweis noch zum Umgang noch mit der Konsole von BlueJ. Ausgaben wie
System.out.printeln(it.next())
erfolgen auf die Systemkonsole des jeweiligen Betriebssystems. Bei Windos ist das üblicherweise ein schwarzes Fenster das zur DOS-Shell gehört. BlueJ fängt diese Ausgaben ab und leitet sie in ein Fenster seiner grafischen Oberfläche um.
Dieses Fenster benimmt sich aber ähnlich wie das DOS-Fenster, es sammeln sich vor allem die Ausgaben unterschiedlicher Programmdurchläufe an, was schnell unübersichtlich wird. Daher bietet BlueJ für dieses Fenster auch eine Löschfunktion an.
Über Optionen -> Konsole löschen steht diese Funktion jederzeit zur Verfügung.
Eine mögliche Lösung für Aufgabe 8 könnte dann folgendermaßen aussehen:
/** * Entferne die Notiz mit der angegebenen Nummer aus * diesem Notizbuch, wenn sie existiert. * @param notiznummer die Nummer der zu entfernenden Notiz. */ public void entferneNotiz(int notiznummer) { if(notiznummer < 0) { // Keine gültige Nummer, nichts zu tun. } else if(notiznummer < anzahlNotizen()) { // Die Notiznummer ist gültig. notizen.remove(notiznummer); } else { // Keine gültige Nummer, nichts zu tun. } }
Aufgabe 9
Erweitere das Projekt Notizbuch um eine Methode zum Anzeigen aller Einträge.
6. Polymorphie
Die Aufgabe 9 dürfte nun nicht mehr besonders kompliziert sein, die Lösung basiert auf einer der angesprochenen Iterationen. Interessant an der folgenden Lösung ist vor allem, dass die Methode den gleichen Namen besitzt, wie die bereits vorhandene Methode zum Löschen eines einzelnen Datensatzes.
/** * Gib alle Notizen dieses Notizbuchs auf die Konsole aus. */ public void zeigeNotiz() { int index = 0; while(index < notizen.size()) { zeigeNotiz(index); index++; } }
Java erlaubt es den gleichen Methodennamen mehrfach zu benutzen, sofern sich zumindest der Interface-Teil der Methoden voneinander unterscheidet, dies bezeichnet man als Polymorphie (Vielgestaltigkeit), genauer als Methoden-Polymorphie (polymorphe Variablen werden etwas später auftauchen).
Aufgabe 10
Erweitere das Projekt Notizbuch um eine Methode zeigeNotiz(String vergleichstext).
Aufgabe 11
Erweitere das Projekt Notizbuch um die Methoden:
- entferneNotiz() zum Löschen aller Datensätze
- entferneNotiz(String vergleichstext) zum Löschen übereinstimmender Datensätze.
Die Aufgabe 10 ist auf den ersten Blick nicht besonders spannend, da unser Notizbuch nicht mehr speichert, als auch im Vergleichstext auftaucht. Bei komplexeren Objekten könnte man aber auf entsprechende Art nach einem Element suchen, das einen bestimmten Feldinhalt besitzt.
Bei der Lösung der Aufgabe 10 taucht ein interessantes Problem auf, nämlich die Frage, wann zwei Strings gleich sind.
/** * Zeige eine Notiz. * @param Text der Notiz, die gezeigt werden soll. */ public void zeigeNotiz(String vergleichstext) { int index = 0; while(index < notizen.size()) { if (vergleichstext.equals(notizen.get(index))) { zeigeNotiz(index); } index++; } }
Der Vergleich der Strings nach dem Muster
if (vergleichstext == notizen.get(index)) ....
funktioniert nicht unbedingt. Genaugenommen wird hier nämlich überprüft, ob die beiden String-Objekte gleich sind, was oft selbst dann nicht der Fall ist, wenn beide Objekte den gleichen Text beinhalten. Stringobjekte sollte man daher nie auf diese Art vergleichen, sondern über die equals-Methode eines der Objekte.
if (vergleichstext.equals(notizen.get(index))) ...
Mögliche Lösungen für Aufgabe 11 zeigt das folgende Listing.
/** * Entfernt alle Notizen dieses Notizbuches. */ public void entferneNotiz() { notizen.clear(); } /** * Entfernt die Notiz mit dem angegebenen Text. * @param Text der Notiz, die entfernt werden soll. */ public void entferneNotiz(String vergleichstext) { int index=0; while (index<anzahlNotizen()) { if (vergleichstext.equals(notizen.get(index))) { entferneNotiz(index); break; } } }
Beim Löschen von Datensätzen taucht ein Problem auf. Der gesamte Rest der Liste verschiebt sich und auch die Anzahl der Elemente verringert sich. Man muss daher sehr aufpassen, wie man weiterarbeitet. Im vorliegenden Listing wurde hier der sicherste Weg gewählt, nämlich die Schleife mittels break verlassen, wenn wenn ein Datensatz gelöscht wurde. Damit können keine Probleme mit dem Index auftauchen, dafür wird aber nur der erste gefundene Datensatz gelöscht.
Das Definieren von mehreren Methoden bezeichnet man als Überladen. Die Methoden sind parallel und unabhängig voneinander vorhanden. Sie müssen sich in ihrer Signatur, der Anzahl bzw. dem Typ der Parameter, unterscheiden.
Eine Methode mit identischer Signatur kann man in abgeleiteten Klassen schrieben. Dann ist nur die neue Methode zugänglich, die geerbte Methode aus der Ursprungsklasse ist dann nicht direkt erreichbar. Dies bezeichnet man als Überschreiben.
7. Sitzgruppe
Im nächsten Schritt soll unser Möbelprojekt um ein zusammengesetztes Objekt erweitert werden, eine Sitzgruppe, die z.B. aus einem Tisch und vier Stühlen besteht, analog zu Aufgabe 5.
Aufgabe 12:
Erstelle eine Klassendiagramm für das Möbelprojekt mit Sitzgruppe
Wenn man etwas über die Aufgabenstellung nachdenkt, dann kommt man schnell zu dem Schluss, dass die Sitzgruppe selber ein Möbelstück sein muss, wie Tisch und Stuhl auch. Sie soll z.B. als Ganzes verschoben werden können. Da sie auf der anderen Seite auch einen Tisch und vier Stühle hat ergibt sich das folgende Klassendiagramm.
Der einfachste Ansatz zur Lösung dieser Aufgabe besteht darin Tisch und Stuhl als Klassenvariablen einzurichten:
public class Sitzgruppe extends Moebelstueck { private int breite; private int tiefe; private Tisch tisch; private Stuhl stuhl1; private Stuhl stuhl2; private Stuhl stuhl3; private Stuhl stuhl4; ...
Damit ist dann an vielen Stellen das weitere Vorgehen klar, z.B. beim Konstruktor.
/** * Konstruktor für Objekte der Klasse Sitzgruppe */ public Sitzgruppe() { super(); breite=220; tiefe=230; tisch = new Tisch(); tisch.bewegeHorizontal(xPosition+70); tisch.bewegeVertikal(yPosition+70); stuhl1 = new Stuhl(); stuhl1.bewegeHorizontal(xPosition+110); stuhl1.bewegeVertikal(yPosition+10); stuhl1.dreheAuf(0); stuhl2 = new Stuhl(); stuhl2.bewegeHorizontal(xPosition+10); stuhl2.bewegeVertikal(yPosition+100); stuhl2.dreheAuf(270); stuhl3 = new Stuhl(); stuhl3.bewegeHorizontal(xPosition+110); stuhl3.bewegeVertikal(yPosition+190); stuhl3.dreheAuf(180); stuhl4 = new Stuhl(); stuhl4.bewegeHorizontal(xPosition+210); stuhl4.bewegeVertikal(yPosition+100); stuhl4.dreheAuf(90); }
Dieser Teil lässt sich kaum geschickter Gestalten, da die Positionen und Ausrichtungen der einzelnen Komponenten vorgegeben werden müssen.
Spannender wird es, wenn es an die einzelnen Nachrichten geht. Im einfachsten Fall reagiert die Sitzgruppe auf eine Nachricht wie zeige(), indem sie diese Nachricht an ihre einzelnen Komponenten weitergibt.
/** * Mache dieses Objekt sichtbar. Wenn es bereits sichtbar ist, tut * sich nichts. */ public void zeige() { istSichtbar = true; tisch.zeige(); stuhl1.zeige(); stuhl2.zeige(); stuhl3.zeige(); stuhl4.zeige(); }
Das funktioniert für fast alle Nachrichten entsprechend. Etwas schwieriger wird es nur für alles, was in irgendeiner Weise mit der Drehung zusammenhängt, also auch für gibAktuelleFigur().
Wenn wir der Sitzgruppe die Nachricht dreheAuf() schicken und die Sitzgruppe diese an ihre Komponenten weiterreicht, dann würden nur die Komponenten innerhalb der Sitzgruppe gedreht, nicht aber die Sitzgruppe. Auf dieses Problem werden wir später noch einmal zurück kommen.
Da wir die Methode gibAktuelleFigur() unbedingt implementieren müssen, sie war in Moebelstück nur abstrakt erklärt, hier erst einmal ein erster Ansatz, der sich weitgehend an der Methode von Tisch und Stuhl orientiert.
/** * Berechnet das zu zeichnende Shape anhand der gegebenen Daten * [ Zum Anzeigen der Attributwerte über Inspect muss hier die Sichtbarkeit * auf public gesetzt werden. ] */ public Shape gibAktuelleFigur() { GeneralPath sitzgruppe = new GeneralPath(); sitzgruppe.append( tisch.gibAktuelleFigur(), false); sitzgruppe.append( stuhl1.gibAktuelleFigur(), false); sitzgruppe.append( stuhl2.gibAktuelleFigur(), false); sitzgruppe.append( stuhl3.gibAktuelleFigur(), false); sitzgruppe.append( stuhl4.gibAktuelleFigur(), false); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); return t1.createTransformedShape(sitzgruppe); }
Aufgabe 13:
Vervollständige die Klasse Sitzgruppe, akzeptiere dabei erst einmal Probleme beim Drehen.
Hinweis:
Bei dieser Gelegenheit bietet es sich an ein kleines Designproblem aus der Klasse Moebelstuck zu korrigieren und alle Objekte mit der Anfangsposition (0,0) zu initialisieren. Dazu müssen nur die Werte im Konstruktor angepasst werden.
public Moebelstueck() { xPosition = 0; yPosition = 0; farbe = "blau"; orientierung = 0; istSichtbar = false; }
Diese Änderung erleichtert die Positionierung der Komponenten.
8. Optimierung der Modellierung
Beim Arbeiten an der Sitzgruppe fällt ein Design-Problem des bisherigen Projektes auf. Die Positionierung ist bisher an zwei unterschiedlichen Stellen geregelt.
- Die Verschiebung (Translation) erfolgt über die Zeichenbefehle in gibAktuelleFigur(),
- die Drehung (Rotation) erfolgt über eine affine Transformation.
Einfacher wird es, wenn man bei Operationen über affine Transformationen regelt. Dazu sind lediglich geringe Veränderungen an der Methode gibAktuelleFigur() notwendig.
Hier die Methode für den Stuhl:
/** * Berechnet das zu zeichnende Shape anhand der gegebenen Daten * [ Zum Anzeigen der Attributwerte über Inspect muss hier die Sichtbarkeit * auf public gesetzt werden. ] */ public Shape gibAktuelleFigur() { GeneralPath stuhl = new GeneralPath(); stuhl.moveTo(0, 0); stuhl.lineTo(0+breite, 0); stuhl.lineTo(0+breite+3, 0+tiefe); stuhl.lineTo(0-3 , 0+tiefe); stuhl.lineTo(0 , 0 ); //Das ist die Umrandung. Das Stuhl bekommt noch eine Lehne stuhl.moveTo(0 , 0 + 5 ); stuhl.lineTo(0 + breite , 0 + 5); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); t1.translate(xPosition, yPosition); return t1.createTransformedShape(stuhl); }
Der Stuhl wird hier ab der Startposition (0/0) erzeugt und erst hinterher über eine Translation verschoben.
Die überarbeitete Methode für den Tisch sieht dann folgendermassen aus.
/** * Berechnet das zu zeichnende Shape anhand der gegebenen Daten * [ Zum Anzeigen der Attributwerte über Inspect muss hier die Sichtbarkeit * auf public gesetzt werden. ] */ public Shape gibAktuelleFigur() { GeneralPath tisch = new GeneralPath(); tisch.append( new Ellipse2D.Double(0 , 0, breite, tiefe), false); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); t1.translate(xPosition, yPosition); return t1.createTransformedShape(tisch); }
Hier ist übrigens gleich eine weitere kleine Inkonsistenz beseitigt, indem auch der Tisch über einen GeneralPath erzeugt wird.
Nach diesen Änderungen lässt sich die Klasse Sitzgruppe recht einfach formulieren. Es sind nur ganz wenige Methoden notwendig.
import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.AffineTransform; /** * * @author Java-MS Groupies * hier claus albowski * nach einer Vorlage von Uwe Debacher, * Michael Kölling und David J. Barnes und Axel Schmolitzky * Bearbeitung Rudolf Wilzek * @version 1.2 (5.5.04) */ public class Sitzgruppe extends Moebelstueck { // Instanzvariablen - ersetzen Sie das folgende Beispiel mit Ihren Variablen private int breite; private int tiefe; private Tisch tisch; private Stuhl stuhl1; private Stuhl stuhl2; private Stuhl stuhl3; private Stuhl stuhl4; /** * Konstruktor für Objekte der Klasse Sitzgruppe */ public Sitzgruppe() { // Instanzvariable initialisieren super(); breite=220; tiefe=230; tisch = new Tisch(); tisch.bewegeHorizontal(xPosition+70); tisch.bewegeVertikal(yPosition+70); stuhl1 = new Stuhl(); stuhl1.bewegeHorizontal(xPosition+110); stuhl1.bewegeVertikal(yPosition+10); stuhl1.dreheAuf(0); stuhl2 = new Stuhl(); stuhl2.bewegeHorizontal(xPosition+10); stuhl2.bewegeVertikal(yPosition+100); stuhl2.dreheAuf(270); stuhl3 = new Stuhl(); stuhl3.bewegeHorizontal(xPosition+110); stuhl3.bewegeVertikal(yPosition+190); stuhl3.dreheAuf(180); stuhl4 = new Stuhl(); stuhl4.bewegeHorizontal(xPosition+210); stuhl4.bewegeVertikal(yPosition+100); stuhl4.dreheAuf(90); } public Shape gibAktuelleFigur() { GeneralPath sitzgruppe = new GeneralPath(); sitzgruppe.append( tisch.gibAktuelleFigur(), false); sitzgruppe.append( stuhl1.gibAktuelleFigur(), false); sitzgruppe.append( stuhl2.gibAktuelleFigur(), false); sitzgruppe.append( stuhl3.gibAktuelleFigur(), false); sitzgruppe.append( stuhl4.gibAktuelleFigur(), false); AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); t1.translate(xPosition, yPosition); return t1.createTransformedShape(sitzgruppe); } /** * Hole die X-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteX() { return xPosition+breite/2; } /** * Hole die Y-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteY() { return yPosition+tiefe/2; } }
Nicht vermeiden lässt sich die Beschreibung der Möbelgruppe, wie sie hier im Konstruktor erfolgt. Die restlichen Operationen lassen sich aber ganz einfach erschlagen, da alle Verschiebungen und die Drehung über gibAktuelleFigur() erledigt werden.
Hier wird zuerst ein Gesamt-Shape erzeugt, das sich aus den einzelnen Shapes zusammen setzt. Dieses Gesamt-Shape wird dann den Transformationen unterzogen.
Dadurch ist auch gewährleistet, dass sich die Sitzgruppe um einen einheitlichen Drehpunkt dreht und nicht die Einzel-Objekte jeweils um ihren Mittelpunkt, was nicht gewünscht wäre.
In der bisherigen Form ist der Aufbau der Sitzgruppe recht starr, wenn man z.B. die Anzahl der Stühle variieren will, dann muss man an mehreren Stellen im Listing Änderungen vornehmen.
Deutlich flexibler wird der Ansatz, wenn man alle Objekte in einer Liste sammelt.
import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.AffineTransform; import java.util.ArrayList; /** * @author Java-MS Groupies * hier claus albowski * nach einer Vorlage von Uwe Debacher, * Michael Kölling und David J. Barnes und Axel Schmolitzky * letzte Bearbeitung Uwe Debacher * @version 1.2 (1.6.04) */ public class Sitzgruppe extends Moebelstueck { // Instanzvariablen private int breite; private int tiefe; private ArrayList bestandteile; /** * Konstruktor für Objekte der Klasse Sitzgruppe */ public Sitzgruppe() { // Instanzvariablen initialisieren super(); breite=220; tiefe=230; bestandteile = new ArrayList(); Moebelstueck hilf; hilf = new Tisch(); hilf.bewegeHorizontal(xPosition+70); hilf.bewegeVertikal(yPosition+70); bestandteile.add(hilf); hilf = new Stuhl(); hilf.bewegeHorizontal(xPosition+110); hilf.bewegeVertikal(yPosition+10); hilf.dreheAuf(0); bestandteile.add(hilf); hilf = new Stuhl(); hilf.bewegeHorizontal(xPosition+10); hilf.bewegeVertikal(yPosition+100); hilf.dreheAuf(270); bestandteile.add(hilf); hilf = new Stuhl(); hilf.bewegeHorizontal(xPosition+110); hilf.bewegeVertikal(yPosition+190); hilf.dreheAuf(180); bestandteile.add(hilf); hilf = new Stuhl(); hilf.bewegeHorizontal(xPosition+210); hilf.bewegeVertikal(yPosition+100); hilf.dreheAuf(90); bestandteile.add(hilf); } public Shape gibAktuelleFigur() { GeneralPath sitzgruppe = new GeneralPath(); for (int index=0; index<bestandteile.size(); index=index+1) { sitzgruppe.append(( (Moebelstueck) bestandteile.get(index)).gibAktuelleFigur(), false); } AffineTransform t1 = new AffineTransform(); t1.rotate(Math.toRadians(orientierung),gibMitteX(),gibMitteY()); t1.translate(xPosition, yPosition); return t1.createTransformedShape(sitzgruppe); } /** * Hole die X-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteX() { return xPosition+breite/2; } /** * Hole die Y-Koordinate des Mittelpunktes * [ Hilfsfunktion für das Drehen. ] */ private int gibMitteY() { return yPosition+tiefe/2; } }
Von der Bedienung her ist diese Lösung vollkommen identisch zur vorherigen. Lediglich beim Inspizieren der Variablen kann man beide Lösungen voneinander unterscheiden.
Mit diesem Ansatz wird es auch möglich Möbelstücke zur Laufzeit zu einer Gruppe hinzu zu fügen. Dazu ist lediglich die folgende neue Methode notwendig.
public void neuerBestandteil(Moebelstueck komponente) { bestandteile.add(komponente); zeichne(); }
Aufgabe 14:
Erweitere das Möbelprogramm um die Möglichkeit Wände zu zeichnen. Diese Wände sollen in der Endausbaustufe auch über Durchbrüche für Türen und Fenster verfügen.
Hier müsste noch etwas zun Thema Type-Cast folgen