Eine kurze Einführung, um das Entwickeln von kleinen Projekten zu erleichtern.
michael.eichberg@dhbw.de, Raum 149B
1.0
Wiederholung etablierter Konzepte
Implementieren einer Klasse für einfache Telefonbucheinträge
Entwickeln Sie eine Klasse TelefonbuchEintrag
mit den Attributen:
Integer telefonnummer
String vorname
String nachname
sowie geeigneten Konstruktoren und allen passenden get- und set-Methoden. Setzen Sie Encapsulation um.
Implementieren Sie weiterhin eine Methode public String toString()
, die den Eintrag in der Form "Vorname Nachname, Telefonnummer" zurückgibt.
Implementieren Sie eine Methode public boolean equals(Object obj)
, die zwei Einträge (d. h. zwei Objekte mit dem dynamischen Typ TelefonbuchEintrag
) als gleich betrachtet, wenn die Telefonnummern gleich sind.
Prüfen Sie - durch das Studium der Dokumentation der Methode java.lang.Object.equals()
- ob Sie die Methode korrekt implementiert haben.
Implementieren Sie eine Methode public int hashCode()
, die einen int
Wert zurückgibt und einen Eintrag halbwegs sinnvoll repräsentiert. Prüfen Sie ob Ihre Implementierung dem Kontrakt der Methode java.lang.Object.hashCode()
entspricht.
Schreiben Sie ein Methode, die drei TelefonbuchEintrag
-Objekte erzeugt. Zwei davon sollen die gleichen Inhalte haben. Prüfen Sie dann, ob die Methoden equals()
und hashCode()
korrekt implementiert sind.
Die Domänenmodellierung hilft uns, die relevanten Konzepte und Ideen einer Domäne zu identifizieren.
Immer dann, wenn wir die (weiteren) Konzepte in einem Bereich verstehen müssen.
Entwickler, Domänenexperten(, Anwender)
Erstellen Sie nur für die anstehenden Aufgaben ein Domänenmodell.
Domain Model ≘ Analysemodell oder auch Konzeptmodell
Curtis’law: […] Good designs require deep application knowledge.[1]
Das Domänenmodell wird erstellt, um die Domäne in Konzepte oder Objekte in der realen Welt aufzuschlüsseln.
Das Modell sollte die Menge der konzeptionellen Klassen identifizieren.
(Das Domänenmodell wird iterativ vervollständigt.)
Es ist die Grundlage für den Entwurf der Software.
Frage
Was sind die relevanten Konzepte/Objekte der Domäne?
Vorgehen
Identifizieren Sie die relevanten Konzepte/Objekte.
(Dies kann zum Beispiel durch das Studieren von existierenden Modellen passieren[Fowler1997] oder durch die Analyse von fachlichen Dokumenten, die die Domäne beschreiben.)
Identifizieren Sie die Attribute der Konzepte/Objekte.
Identifizieren Sie die Beziehungen zwischen den Konzepten/Objekten.
Erstellen Sie ein Klassendiagramm.
Wichtig
Verwenden Sie das Vokabular der Domäne; z. B. sollte ein Modell für eine Bibliothek Namen wie „Ausleiher“ anstelle von „Kunde“ verwenden.
Frage
Wann sollte ich etwas als Attribut oder als Klasse modellieren?
Antwort
Faustregel: Wenn wir uns ein Konzept X in der realen Welt nicht als Zahl, Datum oder Text vorstellen können, dann sollte X wahrscheinlich mit Hilfe einer Klasse modelliert werden und ist kein Attribut.
Die Attribute in einem Domänenmodell sollten vorzugsweise „primitive“ Datentypen in Bezug auf die Domäne sein.
Sehr häufige Datentypen sind: Booleans, Datum, Zahl, Zeichen, String, Adresse, Farbe, Telefonnummer,...
Erwägen Sie die Modellierung von Mengen als Klassen, um Einheiten zuordnen zu können.
Beispiel
Der Datentyp des Attributs „Betrag“ einer Zahlung sollte auch die Währung angeben.
Domänenmodell für ein Kassensystem
Erstellen Sie ein Domänenmodell (d. h. ein UML Klassendiagramm) für ein einfaches Kassensystem basierend auf der folgenden Beschreibung und Ihrem Domänenwissen:
Verkauf abwickeln: Ein Kunde kommt an der Kasse an und möchte einen Artikel kaufen. Der Kassierer verwendet das Kassensystem, um jeden Artikel zu erfassen. Das System zeigt eine laufende Summe und Details zu den einzelnen Positionen an. Der Kunde gibt die Zahlungsinformationen ein, die das System prüft und aufzeichnet. Das System aktualisiert den Warenbestand. Der Kunde erhält eine Quittung vom System und verlässt dann das Geschäft mit den Artikeln.
Hinweis
Denken Sie daran, dass wir im Domänenmodell nur die relevanten Konzepte modellieren sollten; d. h. Klassen und deren Attribute und Beziehungen. Methoden sind hier nicht relevant.
Java Records sind eine spezielle Form von Klassen, die dazu dienen, unveränderliche Daten zu modellieren.
Java Records sind häufig hervorragend geeignet, um Klassen aus Domänenmodellen, die insbesondere der Datenhaltung dienen, zu modellieren.
Java Records sind seit Java 16 verfügbar.
Traditioneller Ansatz
1class Point {
2private final int x;
3private final int y;
45
Point(int x, int y) {
6this.x = x; this.y = y;
7}
89
int x() { return x; }
10int y() { return y; }
1112
public boolean equals(Object o) {
13if (!(o instanceof Point)) return false;
14Point other = (Point) o;
15return other.x == x && other.y == y;
16}
17public int hashCode() {
18return Objects.hash(x, y);
19}
2021
public String toString() {
22return String.format(
23"Point[x=%d, y=%d]", x, y
24);
25}
26}
Implementation mit Java record
1 record Point(int x, int y) {}
1jshell> record Point(int x, int y) {}
2| created record Point
34
jshell> var p = new Point(1,2);
5p ==> Point[x=1, y=2]
Deklaration und Initialisierung
1jshell> var x = p.x();var y = p.y()
2x ==> 1
3y ==> 2
45
jshell> System.out.println(p.toString() + " #" + p.hashCode() )
6Point[x=1, y=2] #33
Verwendung
1jshell> new Point(1,2).hashCode();
233
34
jshell> new Point(1,2) == p
5false
67
jshell> new Point(1,2).equals(p);
8true
Die Getter und Setter heissen bei Records Component Methods. Ein direkter Zugriff auf die Attribute ist nicht möglich:
1jshell> p.x
2| Error:
3| x has private access in Point
4| p.x
5| ^-^
Java Records erben immer von java.lang.Record
.
Die Klasse ist (implizit) final
(und notwendigerweise nicht abstrakt).
Die Felder, die die Komponenten eines Records sind, sind final
und private
.
Ein Record kann keinen weiteren (veränderlichen) Zustand haben.
Verschachtelte/Lokale Records sind möglich sind jedoch immer static
.
Records sind serializable.
Sie haben eine equals()- und hashCode()-Methode, die auf allen Attributen basiert.
Sie haben eine toString()-Methode, die alle Attribute ausgibt.
Sie haben getter-Methoden für alle Attribute.
Sie haben einen Konstruktor, der alle Attribute initialisiert.
Sie können static und non-static Methoden haben.
Sie können interfaces implementieren.
Sie können annotations haben.
Ein Record hat immer einen kanonischen Konstruktor, der alle Attribute initialisiert.
(Ein Record hat nie einen parameterlosen Standardkonstruktor.)
Ein Record kann weitere Konstruktoren haben, die jedoch den kanonischen Konstruktor aufrufen müssen.
Beispiel
1record Point(int x, int y) {
2public Point(int x) { this(x,x); }
3}
Es ist möglich einen kompakten kanonischen Konstruktor zu definieren. Der Code zur Initialisierung der Attribute (z. B. this.x = x;
) wird dann implizit am Ende generiert.
Beispiel
1record Point(int x, int y) {
2Point {
3if (x < 0 || y < 0)
4throw new IllegalArgumentException(
5"Negative Koordinaten sind nicht erlaubt.");
6}
7}
Der primäre Zweck von zusätzlichen Konstruktoren ist es, die Validierung oder Normalisierung der Attribute zu ermöglichen.
Ein einfacher TelefonbuchEintrag mit Java Records
Entwickeln Sie eine Klasse TelefonbuchEintrag mit den Attributen:
int telefonnummer
String vorname
String nachname
verwenden Sie dazu ein Java Record. Führen Sie ggf. eine Normalisierung der Attribute durch (löschen von Leerzeichen am Anfang und Ende). Validieren Sie die übergebenen Werte (Telefonummer muss (hier) größer 0 sein und die Namen müssen mind. einen Buchstaben enthalten. Instanziieren Sie drei Objekte und prüfen Sie, ob die Validierung korrekt funktioniert und die Vergleichbarkeit der Objekte korrekt implementiert ist.
Eine Enum-Deklaration spezifiziert eine neue Enum-Klasse, eine eingeschränkte Art von Klasse, die eine kleine Menge von benannten Klasseninstanzen definiert.
Beispieldeklaration
1 public enum Arbeitstag { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG }
Beispielverwendung (Arbeitstag w = Arbeitstag.FREITAG
)
1jshell> switch(w) {
2case FREITAG -> System.out.println("gleich ist Wochenende");
3default -> System.out.println("noch viel zu tun");
4}
5gleich ist Wochenende
67
jshell> w.values();
8==> Arbeitstag[5] { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG }
910
jshell> Arbeitstag.valueOf("FREITAG").ordinal()
11==> 4
Beispiel
1public enum Arbeitstag {
2MONTAG(1), DIENSTAG(2), MITTWOCH(3), DONNERSTAG(4), FREITAG(5);
34
private final int tag;
56
Arbeitstag( int tag ) {
7if (tag < 1 || tag > 5)
8throw new IllegalArgumentException("Ungültiger Tag: " + tag);
9this.tag = tag;
10}
11}
Deklaration
1enum Operation {
2PLUS {
3double eval(double x, double y) { return x + y; }
4},
5DIVIDED_BY {
6double eval(double x, double y) { return x / y; }
7};
89
abstract double eval(double x, double y);
10}
Verwendung
1jshell> double x = 2;
2jshell> for(var op : Operation.values()) {
3System.out.println(op.name() + " " +op.eval(x,x));
4}
5PLUS 4.0
6DIVIDED_BY 1.0
enum
s - Technische BesonderheitenEnums sind final
oder sealed
(falls es innere Klassen gibt)
geschachtelte Enums sind (implizit) static
der Supertyp alle Enums ist java.lang.Enum
(extends
wird für Enums nicht unterstützt.)
Klonen von Enums (clone()
) ist nicht möglich.
Es können keine Instanzen der Enum Klasse (z. B. der Klassen Wochentag erzeugt werden.)
Enum für Währungen
Deklarieren Sie eine Enum (Currency
) für Währungen (Euro, Pfund etc.).
Es soll möglich sein für eine Währung, das Währungssymbol zu erhalten.
Für jede Währung soll es weiterhin möglich sein, die verfügbaren Stückelungen (denominations) zu erhalten.
Schreiben Sie eine kleine main()
-Methode, um Ihr Enum zu testen.
Bemerkung
Falls Sie die Stückelungen in einem Array zwischenspeichern sollten, dann stellen Sie sicher, dass das Array nicht verändert werden kann, wenn der Nutzer die verfügbaren Stückelungen abfragt.
Craig Larman; Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process; Prentice Hall, 2001
Albert Endres and Dieter Rombach; A Handbook of Software and Systems Engineering; Addison Wesley, 2003
Martin Fowler; Analysis Patterns: Reusable Object Models; Addison-Wesley, 1997
JEP 395: Records; https://openjdk.java.net/jeps/395; zuletzt aktualisiert am 3.2.2024