Prof. Dr. A. Poetzsch-Heffter Dipl.-Inform. N. Rauch Dipl.-Inform. C. Stenzel Technische Universität Kaiserslautern Fachbereich Informatik AG Softwaretechnik Lösungen zum Übungsblatt 12: Entwicklung von Softwaresystemen I (WS 2003/04) Aufgabe 1 Abstrakte Klassen (10 Punkte) a) Die Deklaration der Klassen Fahrzeug, Auto und Motorrad als abstrakt hat zur Folge, dass keine Objekte dieser Klasse erzeugt werden können. Der Vorteil solcher Hierarchie abstrakter Klassen ist, dass sie die Klassifikation der Fahrzeuge nach ihren Eigenschaften wiederspiegeln. Zum Beispiel ist die Fahrgestellnummer eine Eigenschaft, die gemeinsam für die Klassen Auto, Motorrad und Fahrrad gilt. Deshalb ist es sinnvoll, eine abstrakte Oberklasse Fahrzeug über den Klassen Auto, Motorrad und Fahrrad einzuführen, und die Eigenschaft Fahrgestellnummer dieser Klasse zuzuordnen. Die Eigenschaft wird dann von den Unterklassen einheitlich vererbt. (2 Punkte) b) class PKW extends Auto { class LKW extends Auto { public LKW(int fzNr, int geschw){ super(fzNr, geschw, 6); } public boolean istPKW(){ return false; } } public PKW(int fzNr, int geschw){ super(fzNr, geschw, 4); } public boolean istPKW(){ return true; } } (3 Punkte) c) Die Klasse Motorfahrzeug müsste als Unterklasse der Klasse Fahrzeug und Oberklasse der Klassen Auto und Motorrad eingeordnet werden. (1 Punkt) d) Die Attribute in der neuen Klassenhierarchie müssen neu eingeordnet werden. 1 2 3 4 5 6 7 8 9 abstract class Fahrzeug { protected int fahrgestellnummer; public Fahrzeug(int fgNr) { fahrgestellnummer = fgNr; } public int getFahrgestellNummer(){ return fahrgestellnummer; } } 10 11 12 13 14 15 17 19 20 21 22 23 24 25 26 30 31 32 33 abstract class Motorfahrzeug extends Fahrzeug { protected int geschwindigkeit; protected int leistung; public Motorfahrzeug (int fzNr, int g, int l){ super(fzNr); geschwindigkeit = g; leistung = l; } getGeschwindigkeit(){ geschwindigkeit; getLeistung(){ leistung; } 34 35 37 38 39 40 41 42 16 18 29 36 class Fahrrad extends Fahrzeug { public Fahrrad(int fzNr) { super(fzNr); } } public int return } public int return } 27 28 43 44 45 46 47 abstract class Auto extends Motorfahrzeug { protected int anzahlraeder; public Auto(int fzNr,int g, int r, int l){ super(fzNr,g,l); anzahlraeder = r; } public int getAnzahlRaeder() { return anzahlraeder; } public abstract boolean istPKW(); } 48 49 50 51 52 abstract class Motorrad extends Motorfahrzeug { protected boolean beiwagen; public Motorrad(int fzNr, int g, boolean b, int l) { super(fzNr,g,l); beiwagen = b; leistung = l; 53 54 55 56 } public boolean hatBeiwagen(){ return beiwagen; } 57 58 59 60 61 } 63 64 65 66 67 70 71 72 73 74 75 76 62 77 class PKW extends Auto { public PKW(int fzNr, int geschw, int leist){ super(fzNr, geschw, 4, leist); } public boolean istPKW(){ return true; } 68 69 78 79 80 } class LKW extends Auto { public LKW(int fzNr, int geschw, int leist){ super(fzNr, geschw, 6, leist); } public boolean istPKW(){ return false; } } 81 (3 Punkte) e) Die neue Klassenhierarchie graphisch dargestellt: Fahrzeug fahrgestellnummer : int Motorfahrzeug geschwindigkeit : int leistung : int Auto Motorrad anzahlraeder : int LKW Fahrrad beiwagen : int PKW (1 Punkt) Aufgabe 2 Schnittstellentypen (praktisch) (14 Punkte) a) public interface AnklickbareFigur extends Figur,Anklickbar {} (1 Punkt) b) Für einen Kreis ist der Test, ob ein Punkt innerhalb der Figur liegt, einfach: Man muss nur prüfen, ob der Abstand des Punktes (px , py ) zum Mittelpunkt (mx , my ) kleiner oder gleich dem Radius r ist. Also liefert die Testmethode true, wenn (px − mx )2 + (py − my )2 ≤ r2 . public class KreisLsg implements AnklickbareFigur { int x,y,r; // ...siehe Blatt 11 public boolean istAnklickbarBei(int x, int y) { int dx = x-this.x, dy = y-this.y; return (dx*dx+dy*dy <= r*r); } } 2 Etwas schwieriger ist die Behandlung des Dreiecks. Hier muss man prinzipiell überprüfen, wie der Punkt relativ zu den drei Seiten bzw. den entsprechenden Geraden liegt. Nehmen wir an, das Dreieck M P 1 P2 P3 sei entgegen dem Uhrzeigersinn beschrieben. Dann muss der Punkt P links von jeder Seite liegen (bzw. darf nicht rechts von einer Seite liegen, wenn man die Kanten auch zur Figur rechnet). −−−→ −−→ −−−→ −−→ −−−→ −−→ Anders ausgedrückt: Die Winkel ^ P1 P2 , P1 P , ^ P2 P3 , P2 P und ^ P3 P1 , P3 P müssen positiv (0 bis 180◦ ) sein. Um dies schnell zu prüfen, kann auf das Vektorenprodukt im dreidimensionalen Raum zurückgegriffen werden. ax bx a y · bz − a z · by Zur Erinnerung: Sei a = ay und b = by . Dann ist a × b definiert als az · bx − ax · bz Der az bz a x · by − a y · bx Vektor a × b steht dann senkrecht auf a und b, und alle drei Vektoren bilden ein Rechtssystem. Bei zwei Ebenen-Vektoren a und b (z-Komponente jeweils 0) zeigt a × b also nach „oben“ (positive z-Komponente), wenn a und b einen positiven Winkel bilden, anderenfalls nach „unten“. Für den Test, ob ein Punkt innerhalb des Dreiecks liegt, müssen wir also die z-Komponenten der Kreuzprodukte der oben genannten drei Vektorenpaare bilden (die anderen Komponenten sind ohnehin 0). Sind die Eckpunkte entgegen dem Uhrzeigersinn angegeben, liegt der Punkt im Inneren des Dreiecks, wenn keine dieser Komponenten negativ ist. Bei einer Angabe der Eckpunkte im Uhrzeigersinn ist es umgekehrt: Dann liegt der Punkt im Inneren, wenn keine der Komponenten positiv ist. Um beide Fälle einzuschließen testet man einfach, ob die Vorzeichen der drei Komponenten gleich sind (wobei eine 0 als neutral zu behandeln ist). Das funktioniert deshalb, weil es keinen Punkt in der Ebene gibt, der mit allen drei Seitenvektoren einen negativen Winkel bildet. So muss ein Punkt, der außerhalb des Dreiecks liegt, mindestens zu einem Vorzeichenunterschied führen. public class DreieckLsg implements AnklickbareFigur { private int[] xPoints, yPoints; // ...siehe Blatt 11 /* Implementiert die Signum-Funktion */ private int sig(int i) { if (i > 0) return 1; else if (i == 0) return 0; else return -1; } /* Testet, ob zwei Werte das gleiche Vorzeichen haben, wobei 0 als * vorzeichenneutral gewertet wird, also für alle x liefert * istVorzeichenGleich(x,0) true. (Wird gemacht, damit auch Klicks * auf den Rand akzeptiert werden.) */ private boolean istVorzeichenGleich(int a, int b) { int sa = sig(a), sb = sig(b); return (sa == 0 || sb == 0 || sa == sb); } public boolean istAnklickbarBei(int x, int y) { int nz1 = (xPoints[1]-xPoints[0])*(y-yPoints[0]) -(yPoints[1]-yPoints[0])*(x-xPoints[0]), nz2 = (xPoints[2]-xPoints[1])*(y-yPoints[1]) -(yPoints[2]-yPoints[1])*(x-xPoints[1]), nz3 = (xPoints[0]-xPoints[2])*(y-yPoints[2]) -(yPoints[0]-yPoints[2])*(x-xPoints[2]); return (istVorzeichenGleich(nz1,nz2) && istVorzeichenGleich(nz2,nz3)); } } Für Vierecke kann man das beim Dreieck benutzte Verfahren verallgemeinern. Schwierig wird es dann allerdings, wenn man auch konkave Figuren zulässt (Innenwinkel mit mehr als 180 ◦ ). Da wir in dieser Aufgabe aber zei3 gen wollten, dass zum Funktionieren nicht alle Figuren anklickbar sein müssen, lassen wir die Klasse Viereck .. unverändert. ^ (3 Punkte) c) Für die oben angegebene Variante sieht die Subtypbeziehung folgendermaßen aus: Figur Anklickbar AnklickbareFigur Viereck Kreis Dreieck (1 Punkt) d) und e) Zur Realisierung der Verschiebefunktion mit der Maus werden zwei Attribute eingeführt: private Figur active = null; private int lastX,lastY; Die Variable active enthält die aktuell angewählte Figur, solange die Maustaste gedrückt ist, ansonsten null. Die Variablen lastX und lastY speichern die letzten Koordinaten des Mauszeigers während einer Verschiebeoperation. Damit kann das Anwählen und Verschieben folgendermaßen realisiert werden: private void mausGedrueckt(int x, int y) { int size = figures.size(); for (int i = size-1; i >= 0; i--) { Figur f = (Figur)figures.get(i); if (f instanceof AnklickbareFigur) { if (((AnklickbareFigur)f).istAnklickbarBei(x,y)){ figurNachVorne(i+1); active = f; lastX = x; lastY = y; return; } } } } private void mausGezogen(int x, int y) { if (active != null && (lastX != x || lastY != y)) { active.verschiebe(x-lastX,y-lastY); lastX = x; lastY = y; p.repaint(); } } private void mausLosgelassen() { active = null; } (4+5 Punkte) 4 Aufgabe 3 Statische und dynamische Bindung Die Ausgabe des Programms: 10 10 1000 1000 Programmzeile Zeile 15: Zeile 16: Zeile 17: Zeile 18: Zeile 19: Zeile 20: Aufgerufene Methode / verwendetes Attribut Konstruktor Klasse A Konstruktor Klasse B Attribut this.i in B Attribut i in A Attribut i in A Methode m in A Attribut i in A Methode n in A Attribut i in A Methode m in B Attribut i in B Methode m in A Attribut i in A Methode n in B Attribut i in B definiert in Zeile 3 9 8 2 2 5 2 4 2 11 8 5 2 10 8 Bindungsart statisch statisch statisch statisch statisch dynamisch statisch dynamisch statisch dynamisch statisch statisch statisch dynamisch statisch 5 (6 Punkte)