Programmieren in Java Vorlesung 02: Einfache Klassen (Fortsetzung) Prof. Dr. Peter Thiemann Albert-Ludwigs-Universität Freiburg, Germany SS 2015 Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 1 / 51 Inhalt Einfache Klassen Testen für einfache Klassen Vermeiden von sinnlosen Objekten Benennen von Konstanten Vermeiden von magischen Werten Zusammengesetzte Klassen Merkmale Fahrkarten mischen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 2 / 51 Einfache Klassen Zusammenfassung: Arbeiten mit einfachen Klassen Executive Summary 1. Testen I I Synthetische Tests aus Spezifikation Analytische Tests aus Fehlerberichten 2. Es sollte nicht möglich sein, sinnlose Objekte zu erzeugen. I I Factory-Methoden Aufzählungstypen 3. Zur Vermeidung von Missverständnissen und zur Dokumentation sollte jede Konstante sinnvoll benannt werden. 4. Der Wertebereich eines Felds sollte nicht künstlich beschränkt werden. (Keine magischen Werte.) Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 3 / 51 Einfache Klassen Testen für einfache Klassen Testen für einfache Klassen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 4 / 51 Einfache Klassen Testen für einfache Klassen Testen I Ansatz: Unit Testing I separate Testsuite für jede Programmkomponente (z.B. Klasse) I separate Tests für jede Methode zwei Arten von Tests I I synthetische Tests abgeleitet von der Spezifikation I I I Demonstration der normalen Funktion Randfälle Tests, die Fehler dokumentieren I Bug report → fehlschlagender Testfall → Bug fix → funktionierender Testfall I Tests werden nicht wieder entfernt Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 5 / 51 Einfache Klassen Testen für einfache Klassen Testen mit Werkzeugen JUnit I Werkzeug zum Unit Testing I Eclipse-Integration I Rahmen zum automatischen Testen auf Knopfdruck Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 6 / 51 Einfache Klassen Testen für einfache Klassen Testen mit Werkzeugen JUnit I Werkzeug zum Unit Testing I Eclipse-Integration I Rahmen zum automatischen Testen auf Knopfdruck Vorgehensweise I Zu jeder “normalen” Klasse erstelle eine Testklasse I Für jede signifikante Methode erstelle mindestens eine synthetische Testmethode I Für jeden nachvollziehbaren Bug erstelle eine Testmethode Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 6 / 51 Einfache Klassen Testen für einfache Klassen Testklasse zu SimpleTicket 1 package lesson 01; 2 3 4 import org.junit.Test; import static org.junit.Assert.∗; 5 6 7 8 public class SimpleTicketTest { // Testmethoden } I I Testklasse im gleichen Paket Die beiden imports machen public Klassen und Methoden des Testframeworks JUnit sichtbar I org.junit ist ein Paket, das zu JUnit gehört org.junit.Test und org.junit.Assert sind die Klassen Test und Assert in I diesem Paket Erklärung von static folgt . . . I I Namenskonvention für Testklasse: <IhreKlasse>Test Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 7 / 51 Einfache Klassen Testen für einfache Klassen Testen von Methoden: Beispiel Spezifikation isUsable() isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 8 / 51 Einfache Klassen Testen für einfache Klassen Testen von Methoden: Beispiel Spezifikation isUsable() isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Synthetischer Test 1 2 3 4 5 @Test public void testIsUsable() { SimpleTicket st = new SimpleTicket(1, 1); assertTrue(”Ticket should be usable”, st.isUsable()); } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 8 / 51 Einfache Klassen Testen für einfache Klassen Testen von Methoden: Beispiel Spezifikation isUsable() isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Synthetischer Test 1 2 3 4 5 @Test public void testIsUsable() { SimpleTicket st = new SimpleTicket(1, 1); assertTrue(”Ticket should be usable”, st.isUsable()); } I @Test: Markierung (Annotation) für eine Testmethode Deklaration einer lokalen Variable mit Typ new SimpleTicket (1, 1): Erzeuge ein neues Objekt der Klasse SimpleTicket und rufe den Konstruktor mit den Parameter (1, 1) auf st.isUsable(): Aufruf der Method auf dem Objekt st assertTrue(message, condition): Prüft, ob condition erfüllt ist. Sonst Exception. I SimpleTicket st: I I I Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 8 / 51 Einfache Klassen Testen von Methoden: Spezifikation isUsable(), Testen für einfache Klassen Teil 2 isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 9 / 51 Einfache Klassen Testen von Methoden: Spezifikation isUsable(), Testen für einfache Klassen Teil 2 isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Synthetischer Test 1 2 3 4 5 6 7 8 @Test public void testIsUsable2() { SimpleTicket st = new SimpleTicket(1, 1); st.stamp(4711, 1); assertFalse(”Ticket should not be usable”, st.isUsable()); st.stamp(5000, 2); assertFalse(”Ticket should still be unusable”, st.isUsable()); } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 9 / 51 Einfache Klassen Testen von Methoden: Spezifikation isUsable(), Testen für einfache Klassen Teil 2 isUsable() Fahrschein ist verwendbar, wenn er noch nicht abgestempelt worden ist. Synthetischer Test 1 2 3 4 5 6 7 8 @Test public void testIsUsable2() { SimpleTicket st = new SimpleTicket(1, 1); st.stamp(4711, 1); assertFalse(”Ticket should not be usable”, st.isUsable()); st.stamp(5000, 2); assertFalse(”Ticket should still be unusable”, st.isUsable()); } I assertFalse(message, condition): Prüft, ob condition nicht erfüllt ist. Sonst Exception. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 9 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Vermeiden von sinnlosen Objekten Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 10 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Vermeiden von sinnlosen Objekten Erinnerung (Felder und Konstruktor von 1 2 3 4 5 6 7 8 9 SimpleTicket) // Preisstufe 1, 2, 3 private int level; // Kind = 0, Erwachsener = 1 private int category; // ... public SimpleTicket(int level, int category) { this.level = level; this.category = category; } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 11 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Vermeiden von sinnlosen Objekten Erinnerung (Felder und Konstruktor von 1 2 3 4 5 6 7 8 9 SimpleTicket) // Preisstufe 1, 2, 3 private int level; // Kind = 0, Erwachsener = 1 private int category; // ... public SimpleTicket(int level, int category) { this.level = level; this.category = category; } Sinnvolle und sinnlose Objekte I Keine Restriktion der Argumente beim Aufruf des Konstruktors I Sinnvoll: new SimpleTicket(1, 1); new SimpleTicket(1, 0) I Sinnlos: new SimpleTicket(0, 3); new SimpleTicket(−1, 42); new SimpleTicket(−999, −999) Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 11 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Vermeiden der Konstruktion von sinnlosen Objekten Wanted Sinnlose Objekte können nicht erzeugt werden, d.h., alle Objekte sind sinnvoll. Zwei Strategien 1. Fehlermeldung beim Erzeugen (Factory Method) + geht immer, auch bei komplizierten Bedingungen an das Objekt − potentieller Fehler zur Laufzeit 2. Einschränkung des Wertebereichs − nur Bedingungen an einzelne Konstruktorargumente + keine Laufzeitfehler Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 12 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Strategie #1: Absicherung durch Factory Method I Eine Java Methode kann einen Fehlerzustand durch eine Exception melden. I Eine Exception wird durch eine throw-Anweisung ausgelöst und kann durch eine catch-Anweisung abgefangen werden. I Jede Exception wird durch ein geeignetes Exception-Objekt repräsentiert ⇒ Die throw-Anweisung nimmt ein Exception-Objekt als Parameter I Konvention: vermeide Exceptions in Konstruktoren Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 13 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Strategie #1: Absicherung durch Factory Method I Eine Java Methode kann einen Fehlerzustand durch eine Exception melden. I Eine Exception wird durch eine throw-Anweisung ausgelöst und kann durch eine catch-Anweisung abgefangen werden. I Jede Exception wird durch ein geeignetes Exception-Objekt repräsentiert ⇒ Die throw-Anweisung nimmt ein Exception-Objekt als Parameter I Konvention: vermeide Exceptions in Konstruktoren Lösung: Das Pattern factory method Verstecke den Konstruktor und definiere (eine) Methode(n) zur Konstruktion von Objekten. Diese wirft eine Exception, falls die Argumente fehlerhaft sind. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 13 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Strategie #1: Absicherung durch Factory Method Implementierung Teil 1 1 2 3 4 5 private SimpleTicket(int level, int category) { this.level = level; this.category = category; } // next up: constructor method create() I Sichtbarkeit des Konstruktors wird private. I Anstelle von new SimpleTicket(l, c) verwende Aufrufe der factory-Methode create (nächste Folie): SimpleTicket.create (l, c) I Die factory-Methode hat die gleichen Parameter wie der Konstruktor, aber sie prüft ihre Legalität bevor das Objekt erzeugt wird. I Die factory-Methode ist eine static Methode: Sie hängt nur von der Klasse ab, nicht von einem spezifischen Objekt. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 14 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Strategie #1: Absicherung durch Factory Method Implementierung Teil 2 1 2 3 4 5 6 7 8 9 10 11 12 /∗∗ ∗ Safe constructor method for simple tickets. ∗ @param level must be 1,2,3 ∗ @param category must be 0,1 ∗ @return a new legal SimpleTicket ∗/ public static SimpleTicket create(int level, int category) { if (level < 1 || level > 3 || category < 0 || category > 1 ) { throw new IllegalArgumentException(”Illegal level or category”); } return new SimpleTicket(level, category); } I I Eine static Methode hängt nicht an einem Objekt, sondern kann direkt über die Klasse als SimpleTicket.create() aufgerufen werden. IllegalArgumentException ist ein vordefinierter Konstruktor für ein Exception Objekt. Das String-Argument ist Teil der Fehlermeldung. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 15 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Strategie #2: Einschränkung des Wertebereichs Aufzählungstyp I I Anstatt die Kategorie “Kind” oder “Erwachsener” durch Zahlen zu kodieren, verwende einen Aufzählungstyp (enum). Ein Aufzählungstyp I I I ist definiert durch die Liste seiner benannten Werte, Elemente genannt dient der Modellierung einer endlichen Zustandsmenge Definierbar im Klassendiagramm und im Java-Code. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 16 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Klassendiagramm (Revision mit Aufzählungstyp) Enumeration TicketCategory CHILD ADULT SimpleTicket level : PriceLevel category : TicketCategory timestamp : long zone : FareZone Enumeration FareZone ZONE A ZONE B ZONE C Enumeration PriceLevel LEVEL 1 LEVEL 2 LEVEL 3 Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 17 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Aufzählungstypen TicketCategory und FareZone 1 package lesson 02; 2 5 public enum TicketCategory { CHILD, ADULT; } 1 package lesson 02; 3 4 2 3 4 5 public enum FareZone { ZONE A, ZONE B, ZONE C; } I Syntax: Sichtbarkeit, Schlüsselwort enum, Name (groß) I Elemente des Aufzählungstyps: Bezeichner, nur Großbuchstaben oder Unterstriche Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 18 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket Vorher 9 10 11 12 13 14 15 16 17 // Preisstufe 1, 2, 3 private int level; // Kind = 0, Erwachsener = 1 private int category; // Zeitstempel der Entwertung (in Millisekunden seit 1.1.1970) // nicht entwertet=0, ungültig=1 private long timestamp; // Ort der Entwertung: Zone A=1, B=2, C=3 private int zone; Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 19 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket Nachher 4 5 6 7 8 9 10 11 12 // new: Preisstufe: LEVEL 1, LEVEL 2, LEVEL 3 private PriceLevel level; // new: Kind = CHILD, Erwachsener = ADULT private TicketCategory category; // Zeitstempel der Entwertung (in Millisekunden seit 1.1.1970) // nicht entwertet=0, ungültig=1 private long timestamp; // new: Ort der Entwertung: ZONE A, ZONE B, ZONE C private FareZone zone; Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 20 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket.validate() Vorher 1 2 3 4 5 6 7 8 9 10 11 12 13 public boolean validate(int c, long t, int z) { // 1. stamped exactly once? boolean result = (this.timestamp != 0) && (this.timestamp != 1); // 2. passenger category less than ticket category? result = result && (c <= category); // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level ∗ 60 ∗ 60 ∗ 1000); // 4. ticket used in valid zone? int leveldiff = Math.abs(zone − z); result = result && (leveldiff < level); return result; } I Operationen in Zeile 5 (numerischer Vergleich), 8 (Rechnung), 11 (numerischer Vergleich) sind auf Aufzählungstypen nicht definiert. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 21 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket.validate() Nachher 1 2 3 4 5 6 7 8 9 10 public boolean validate(TicketCategory c, long t, FareZone z) { ... // 2. passenger category less than ticket category? result = result && (c.ordinal() <= category.ordinal()); ... // 4. ticket used in valid zone? int leveldiff = Math.abs(zone.ordinal() − z.ordinal()); ... return result; } I Nachher: geänderte Signatur I Abhilfe: auf Aufzählungstypen ist Methode int ordinal() definiert. I ordinal() bildet auf 0, 1, 2, 3, . . . ab Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 22 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket.validate() Vorher 1 2 3 4 5 6 7 8 public boolean validate(int c, long t, int z) { ... // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level ∗ 60 ∗ 60 ∗ 1000); ... return result; } I Fürs Rechnen mit level ist ordinal() nicht passend. I Besser wäre LEVEL 1 7→ 1, LEVEL 2 7→ 2 etc I Das kann durch einen Aufzählungstyp mit Attributen erreicht werden. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 23 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Einschub: Aufzählungstyp mit Attribut 2 3 4 5 6 7 8 9 10 11 12 13 public enum PriceLevel { LEVEL 1(1), LEVEL 2(2), LEVEL 3(3); // corresponding numeric level private final int level; private PriceLevel(int level) { this.level = level; } // getter method public int getLevel() { return this.level; } } I Für jedes Attribut wird ein private final Feld definiert. bedeutet, dass sich der Wert des Feldes nach der Initialisierung durch den Konstruktor nicht mehr ändern kann. Konstruktoraufruf mit Parametern in Klammern (Zeile 4) public Getter-Methode nur zum Lesen des Feldes I final I I Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 24 / 51 Einfache Klassen Vermeiden von sinnlosen Objekten Änderungen in SimpleTicket.validate() Nachher 1 2 3 4 5 6 7 8 public boolean validate(TicketCategory c, long t, FareZone z) { ... // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level.getLevel() ∗ 60 ∗ 60 ∗ 1000); ... return result; } I Erstes Ziel erreicht: keine sinnlosen SimpleTicket Objekte mehr möglich! I Aber die Klasse enthält weitere Anti-Pattern, die wir eliminieren wollen . . . Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 25 / 51 Einfache Klassen Benennen von Konstanten Benennen von Konstanten Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 26 / 51 Einfache Klassen Benennen von Konstanten Benennen von Konstanten Betrachte 1 2 3 // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level.getLevel() ∗ 60 ∗ 60 ∗ 1000); I Was ist die Bedeutung der Konstante 60 ∗ 60 ∗ 1000? Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 27 / 51 Einfache Klassen Benennen von Konstanten Benennen von Konstanten Betrachte 1 2 3 // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level.getLevel() ∗ 60 ∗ 60 ∗ 1000); I Was ist die Bedeutung der Konstante 60 ∗ 60 ∗ 1000? I Natürlich ist es die Anzahl der Millisekunden pro Stunde! I Zur Vermeidung von Missverständnissen und zur Dokumentation sollte jede Konstante sinnvoll benannt werden. I Ausnahmen: 0, 1, true, false etc Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 27 / 51 Einfache Klassen Benennen von Konstanten Benennen von Konstanten Betrachte 1 2 3 // 3. ticket expired? long timediff = t − timestamp; result = result && (timediff <= level.getLevel() ∗ 60 ∗ 60 ∗ 1000); I Was ist die Bedeutung der Konstante 60 ∗ 60 ∗ 1000? I Natürlich ist es die Anzahl der Millisekunden pro Stunde! I Zur Vermeidung von Missverständnissen und zur Dokumentation sollte jede Konstante sinnvoll benannt werden. I Ausnahmen: 0, 1, true, false etc I Sinnvolles Muster: Lege separate Klasse zur Aufnahme der Konstantendefinitionen an. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 27 / 51 Einfache Klassen Benennen von Konstanten Klasse für Konstantendefinitionen 1 2 3 public class Tickets { public static final int MILLISECONDS PER HOUR = 60 ∗ 60 ∗ 1000; } I static I final — gehört zu keinem Objekt — Konstante, die nach Initialisierung nicht mehr verändert wird I Name in Großbuchstaben oder Unterstrichen I Initialisiert durch konstanten Ausdruck I Verwendung durch Tickets.MILLISECONDS PER HOUR Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 28 / 51 Einfache Klassen Vermeiden von magischen Werten Vermeiden von magischen Werten Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 29 / 51 Einfache Klassen Vermeiden von magischen Werten Vermeiden von magischen Werten // Zeitstempel der Entwertung (in Millisekunden seit 1.1.1970) // nicht entwertet=0, ungültig=1 private long timestamp; 1 2 3 I Die Werte 0 und 1 sind magisch für timestamp. I Sie sind prinzipiell gültige Zeitstempel, bei denen es höchst unwahrscheinlich ist, dass sie auftreten. I Besser und sicherer: verfahre nach dem Prinzip Nichts ist unmöglich“ ” Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 30 / 51 Einfache Klassen Vermeiden von magischen Werten Vermeiden von magischen Werten Lösung // Zeitstempel der Entwertung (in Millisekunden seit 1.1.1970) private long timestamp; 1 2 3 private static final int MAX RIDES = 1; private int nrOfStamps; 4 5 I Neues Feld nrOfStamps, das die Anzahl der Stempel zählt. ist die maximale Anzahl von Stempeln, bevor der Fahrschein nicht mehr entwertet werden kann. I MAX RIDES I timestamp unterliegt keinerlei Einschränkungen mehr. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 31 / 51 Einfache Klassen Vermeiden von magischen Werten Vermeiden von magischen Werten Anpassung der Methoden 1 2 3 1 2 3 4 5 6 7 public boolean isUsable() { return nrOfStamps < MAX RIDES; } public void stamp(long t, FareZone z) { if (isUsable()) { zone = z; timestamp = t; } nrOfStamps++; } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 32 / 51 Zusammengesetzte Klassen Zusammengesetzte Klassen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 33 / 51 Zusammengesetzte Klassen Erweiterung: Mehrfahrtenkarte Spezifikation Das Verkehrsunternehmen möchte nun 2x4-Fahrtenkarten ausgeben. Die 2x4-Fahrtenkarte hat eine Preisstufe (1, 2, 3), sie ist für Erwachsene oder Kinder und sie kann acht Mal entwertet werden. Der Entwerterstempel enthält Uhrzeit, Datum und Ort der Entwertung. (usw. wie gehabt) Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 34 / 51 Zusammengesetzte Klassen Klassendiagramm (Versuch) SimpleTicket level : PriceLevel category : TicketCategory timestamp : long[] zone : FareZone[] nrOfStamps : int I Erste Idee I I I Erweitere timestamp und zone zu Arrays Korrespondierende Einträge bilden einen Stempel ab. Nachteil: Korrespondenz ist implizit Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 35 / 51 Zusammengesetzte Klassen Validation wird Klasse SimpleTicket level : PriceLevel 1 category : TicketCategory nrOfStamps : int Validation timestamp : long 0,1 zone : FareZone timeSinceCreated(long t) : long levelDifference(FareZone z) : int Besser I Eigene Klasse Validation I Assoziation zwischen SimpleTicket und Validation I Jedes SimpleTicket besitzt null oder eine Validation I Ein Validation Objekt kapselt einen Stempel I Operationen auf Zeitstempeln müssen in die Klasse Validation verlagert werden! Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 36 / 51 Zusammengesetzte Klassen Validation in Java 1 2 3 4 5 public class Validation { // Zeitstempel der Entwertung (in Millisekunden seit 1.1.1970) private final long timestamp; // Ort der Entwertung private final FareZone zone; 6 public Validation(long timestamp, FareZone zone) { this.timestamp = timestamp; this.zone = zone; } /∗∗ ... ∗/ public long timeSinceCreated(long t) { return t − timestamp; } /∗∗ ... ∗/ public int levelDifference(FareZone z) { return Math.abs(zone.ordinal() − z.ordinal()); } 7 8 9 10 11 12 13 14 15 16 17 18 19 } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 37 / 51 Zusammengesetzte Klassen Anpassung in SimpleTicket 1 2 3 4 5 6 7 8 9 10 11 /∗∗ ∗ Stamp the ticket. ∗ @param t time of validation (in millisec) ∗ @param z location of validation (zone) ∗/ public void stamp(long t, FareZone z) { if (this.isUsable()) { validation = new Validation(t, z); } nrOfStamps++; } I Die stamp() Methode erzeugt ein neues Validation Objekt. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 38 / 51 Zusammengesetzte Klassen Anpassung in SimpleTicket 1 2 3 4 5 6 7 8 9 10 11 /∗∗ ∗ Check if a ticket is valid given the passenger’s category, ... ∗/ public boolean validate(TicketCategory c, long t, FareZone z) { boolean result = nrOfStamps == 1; result = result && (c.ordinal() <= category.ordinal()); result = result && (validation.timeSinceCreated(t) <= level.getLevel() ∗ Tickets.MILLISECONDS PER HOUR); result = result && (validation.levelDifference(z) < level.getLevel()); return result; } I validate() I bearbeitet nur eigene Felder Alle Berechnungen an Zeitstempeln sind in die Klasse Validation verschoben Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 39 / 51 Zusammengesetzte Klassen Mehrfahrtenkarte mit Validation TwoTimesFourTicket level : PriceLevel 1 category : TicketCategory nrOfStamps : int Validation timestamp : long 0..8 zone : FareZone timeSinceCreated(long t) : long levelDifference(FareZone z) : int Änderung I Assoziation zwischen TwoTimesFourTicket und Validation I Jedes TwoTimesFourTicket besitzt null bis acht Validation Objekte I (Vielfachheit / Multiplicity einer Assoziation) I Mögliche Implementierung: mit Array Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 40 / 51 Zusammengesetzte Klassen Implementierung der Mehrfahrtenkarte (Auszug) 1 2 3 public class TwoTimesFourTicket { // ... private Validation[] validation; 4 public TwoTimesFourTicket(PriceLevel level, TicketCategory category) { this.level = level; this.category = category; this.validation = new Validation[MAX RIDES]; this.nrOfStamps = 0; } 5 6 7 8 9 10 11 public void stamp(long t, FareZone z) { if (isUsable()) { validation[nrOfStamps] = new Validation(t, z); } nrOfStamps++; } 12 13 14 15 16 17 18 } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 41 / 51 Zusammengesetzte Klassen Merkmale Merkmale zusammengesetzter Klassen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 42 / 51 Zusammengesetzte Klassen Merkmale Merkmale I SimpleTicket und TwoTimesFourTicket sind Beispiele für zusammengesetzte Klassen. I Zusammengesetzte Klassen sind mit anderen Klassen assoziiert oder besitzen Attribute, die selbst Objekte sind I Methodenentwurf: Eine Operation auf einer zusammengesetzten Klasse verwendet I I I Attribute und Operationen der Klasse Operationen auf Objekten von assoziierten Klassen Operationen auf Objekten in Attributen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 43 / 51 Zusammengesetzte Klassen Merkmale Testen für zusammengesetzte Klassen I Entwurf von Operationen geschieht top-down 1. Entwerfe Operation auf zusammengesetzter Klasse unter Annahme von Operationen auf assoziierten Klassen 2. Entwerfe geforderte Operationen auf den assoziierten Klassen I Testen geschieht bottom-up 1. Erstelle Tests für untergeordnete und assoziierte Klassen 2. Erstelle Tests für zusammengesetzte Klasse I Im Beispiel 1. Erstelle Tests für Validation 2. Erstelle Tests für SimpleTicket und TwoTimesFourTicket Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 44 / 51 Zusammengesetzte Klassen Fahrkarten mischen Fahrkarten mischen Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 45 / 51 Zusammengesetzte Klassen Fahrkarten mischen Erweiterung: Fahrkarten mischen Spezifikation Das Verkehrsunternehmen möchte Einzelfahrscheine und auch 2x4-Fahrtenkarten ausgeben. (usw. wie gehabt) Ein Fahrgast möchte mit Einzelfahrscheinen und 2x4-Fahrtenkarten gleichermaßen umgehen. I D.h., ein Einzelfahrschein und eine 2x4-Fahrtenkarte muss sich auf eine gewisse Art gleich verhalten. I Sie müssen beide das gleiche Interface implementieren! Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 46 / 51 Zusammengesetzte Klassen Fahrkarten mischen Klassendiagramm: Interface für Fahrscheine Interface ITicket isUsable() : boolean stamp(when : long, where : int) : void validate(int category, when : long, where : int) : boolean I Wie Klasse, aber Stereotyp Interface“ ” Keine Attribute (Konstanten möglich) I Operationen wie Klasse I Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 47 / 51 Zusammengesetzte Klassen Fahrkarten mischen Interface in Java 1 package lesson 02; 2 3 4 5 6 7 1 2 public interface ITicket { public boolean isUsable(); public void stamp(long t, FareZone z); public boolean validate(TicketCategory c, long t, FareZone z); } I Jede Klasse, die diese Methoden public implementiert, kann das Interface implementieren I Intention durch die implements-Klausel im Klassenheader deklariert. I Dann müssen diese Methoden vorhanden sein. public class SimpleTicket implements ITicket { ... } public class TwoTimesFourTicket implements ITicket { ... } Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 48 / 51 Zusammengesetzte Klassen Fahrkarten mischen Implementierungsbeziehung im Klassendiagramm Interface ITicket isUsable() stamp() validate() ∆ SimpleTicket I TwoTimesFourTicket Gestrichelte Linie mit offenem Pfeilkopf von Klasse zu implementiertem Interface Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 49 / 51 Zusammengesetzte Klassen Fahrkarten mischen Interfacetypen I Ein Interface definiert einen Referenztypen, genau wie eine Klassendeklaration. I Ein Interface (-typ) kann überall benutzt werden, wo ein Typ erwartet wird: lokale Variable, Felder, Parameter, usw. I Wenn ein Interfacetyp als Parameter erwartet wird, dann darf ein Objekt einer beliebigen Klasse, die das Interface implementiert, übergeben werden. I Gleiches gilt für Zuweisungen an Variable oder Felder vom Interfacetyp. Jede implementierende Klasse ist ein Subtyp des Interfacetyps. Also I I I SimpleTicket ist Subtyp von ITicket und TwoTimesFourTicket ist Subtyp von ITicket. Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 50 / 51 Zusammengesetzte Klassen Fahrkarten mischen Verwendung von Interfacetypen Vereinigung von Klassen I Ein Typ I ist eine Vereinigung von Klassen C1 , . . . , Cn , falls jedes Objekt vom Typ I genau eine der Klassen C1 , . . . , Cn besitzt. I Die Operationen auf I sind die gemeinsamen Operationen von C1 , . . . , Cn . I Implementierung durch Composite Pattern. I Der Vereinigungstyp I wird durch ein Interface implementiert. Beispiel für Vereinigung ITicket ist Vereinigung von SimpleTicket und TwoTimesFourTicket Peter Thiemann (Univ. Freiburg) Programmieren in Java JAVA 51 / 51