Projekt Zahlen
Dieses Kapitel basiert auf dem Projektarbeitsblatt zur Zahlenmodellierung. Dort wird ein zusammenhaengendes OOP-Projekt entwickelt, das mehrere Klassen und Interfaces kombiniert: NatuerlicheZahl, GanzeZahl und RationaleZahl samt Inversions- und Rechenoperationen.
Projektidee und Modellierungsziel
Die Kernidee ist, Zahltypen nicht nur als primitive int/double zu behandeln, sondern als eigene Objekte mit fachlichen Regeln.
Damit werden zentrale OOP-Ziele trainiert:
- Invarianten absichern
- Schnittstellen ueber Interfaces definieren
- Operationen typgerecht modellieren
- Konstruktoren und Hilfsmethoden sauber strukturieren
Baustein 1: NatuerlicheZahl
Fachregel
Eine natuerliche Zahl ist nicht negativ.
Das Arbeitsblatt gibt bereits ein Muster vor:
public class NatuerlicheZahl {
public static final NatuerlicheZahl NULL = new NatuerlicheZahl(0);
public static final NatuerlicheZahl EINS = new NatuerlicheZahl(1);
private int wert;
public NatuerlicheZahl(int wert) {
if (wert < 0) this.wert = 0;
else this.wert = wert;
}
}
Wichtiger Entwurfspunkt:
- Die Invariante wird direkt im Konstruktor erzwungen.
Baustein 2: GanzeZahl
GanzeZahl wird im Blatt ueber Vorzeichen + Betrag modelliert.
vorzeichenin{-1, 0, 1}betragalsNatuerlicheZahl
Damit wird nicht nur gerechnet, sondern ein internes Modell mit klaren Regeln aufgebaut.
Konstruktionsregeln laut Blatt
- Ein zentraler Konstruktor mit
intist die Hauptlogik. - Andere Konstruktoren sollen darauf aufbauen.
NULLundEINSals Konstanten bereitstellen.
Rechenoperationen
addiere, multipliziere, subtrahiere sollen neue Objekte liefern statt Seiteneffekte auf bestehende Instanzen auszufuehren.
Das macht Objekte leichter testbar und reduziert unerwartete Nebenwirkungen.
Baustein 3: RationaleZahl
RationaleZahl kombiniert:
- Zaehler als
GanzeZahl - Nenner als
NatuerlicheZahl
Der Nenner darf nie 0 sein. Das Blatt gibt dafuer eine Korrekturregel vor (z. B. auf 1 setzen, falls ungueltig).
Zusatzregel:
- Falls der Nenner negativ uebergeben wird, Vorzeichen in den Zaehler ziehen, Nenner positiv machen.
Kuerzen mit ggT
Das Blatt enthaelt explizit eine ggT-Methode und fordert ein internes selbstKuerzen().
Ziel:
- konsistente kanonische Darstellung
- z. B.
2/4wird intern zu1/2
Das ist ein klassischer Fall fuer private Hilfsmethoden, die nach jeder relevanten Konstruktion oder Operation aufgerufen werden.
Interfaces und Inversen
Das Projekt nutzt Interfaces wie:
AdditivInvertierbarMultiplikativInvertierbarZahlMitVorzeichen
Didaktischer Kern:
- gemeinsame Faehigkeiten als Vertrag definieren
- konkrete Umsetzung in Klassen spezifizieren
Beispielidee:
- additives Inverses von
xist-x - multiplikatives Inverses von
xist1/x(ausserx == 0)
Das Blatt legt fest: Wenn kein multiplikatives Inverses existiert, soll null geliefert werden.
Rueckgabetypen und Kovarianz
Eine sehr gute Modellierungsfrage aus dem Arbeitsblatt:
Warum darf eine Methode aus einem Interface in einer konkreten Klasse einen spezielleren Rueckgabetyp verwenden?
Beispiel:
- Interface:
AdditivInvertierbar additivesInverses(); - Klasse:
GanzeZahl additivesInverses();
Das ist in Java als kovarianter Rueckgabetyp erlaubt und verbessert Nutzbarkeit.
Rechenmethoden als reine Funktionen
Fuer RationaleZahl nennt das Blatt die Operationen:
addieresubtrahieremultiplizieredividiere
Regel:
- Jede Methode liefert eine neue Instanz.
subtrahieresolladditivesInversesnutzen.dividieresollmultiplikativesInversesnutzen.
Diese Wiederverwendung reduziert Fehler und haelt Logik konsistent.
String-Darstellung
Das Arbeitsblatt fordert:
GanzeZahl: uebliche Darstellung wie"-3"RationaleZahl: Darstellung"a/b", z. B."2/3"
Eine saubere toString-Methode ist wichtig fuer Tests, Debugging und Konsolenausgaben.
Warum GanzeZahl keine Subklasse von RationaleZahl sein muss
Das Blatt stellt bewusst diese Modellierungsfrage, obwohl mathematisch Z als Teilmenge von Q gilt.
In OOP ist Teilmengenbeziehung nicht automatisch Vererbungsbeziehung.
- Mathematische Einbettung bedeutet nicht zwingend passende Implementierungsvererbung.
- Vererbung muss API- und Verhaltenskompatibilitaet sichern.
Das ist eine zentrale Designlektion.
Teststrategie fuer das Projekt
Das Arbeitsblatt fordert explizit Testrechnungen in main. Sinnvoll ist eine strukturierte Matrix:
- Konstruktor-Randfaelle
-5beiNatuerlicheZahl, Nenner0, Nenner negativ - Inversen
additiv und multiplikativ inkl. Sonderfall
0 - Rechenoperationen Addition, Multiplikation, Subtraktion, Division
- Normalisierung kuerzen, Vorzeichenposition, String-Ausgabe
Beispiel fuer robuste Konstruktion einer rationalen Zahl
public RationaleZahl(int zaehler, int nenner) {
if (nenner < 0) {
zaehler *= -1;
nenner *= -1;
}
if (nenner == 0) {
nenner = 1;
}
this.zaehler = new GanzeZahl(zaehler);
this.nenner = new NatuerlicheZahl(nenner);
selbstKuerzen();
}
Dieses Muster deckt die wichtigsten Invarianten direkt ab.
Typische Fehlerquellen
- Invarianten nur teilweise geprueft z. B. Nennerregel bei einem Konstruktor vergessen.
- Rechenmethoden veraendern
thisFuehrt zu schwer testbaren Seiteneffekten. - ggT ohne Absolutwertbehandlung Negativwerte koennen kuerzen stoeren.
- Uneinheitliche Vorzeichenlogik Vorzeichen mal im Zaehler, mal im Nenner.
null-Rueckgaben unkontrolliert verwendet Bei Division durch0muss der Aufrufernullpruefen.
Vertiefungsaufgaben
- Fuege zu allen Klassen
equalsundhashCodehinzu. - Ergaenze Vergleichsmethoden (
istKleinerAls,compareTo). - Implementiere Parser aus String (
"-3/7"). - Ersetze
nullbei fehlendem Inversen durch Exception oder Optional und diskutiere Vor-/Nachteile. - Schreibe JUnit-Tests fuer alle Konstruktor-Randfaelle und Rechenregeln.
Zusammenfassung
Das Zahlenprojekt ist ein vollwertiges OOP-Trainingsfeld:
- mathematische Regeln als Objektinvarianten
- Interfaces als Faehigkeitsvertraege
- Konstruktorverkettung und Kapselung
- Operationen als neue Objekte
Damit wird der Uebergang von Einzeluebungen zu zusammenhaengendem Klassendesign sauber vollzogen.
Invarianten je Klasse explizit festhalten
Fuer das Zahlenprojekt ist eine schriftliche Invariantenliste sehr hilfreich.
NatuerlicheZahl
wert >= 0NULLundEINSverweisen auf gueltige Objekte
GanzeZahl
vorzeichenist nur-1,0oder1betragist nichtnull- Bei Wert
0gilt Vorzeichen ebenfalls0
RationaleZahl
nenner > 0nenner != 0- Bruch liegt gekuerzt vor
- Vorzeichen sitzt im Zaehler
Diese Regeln bestimmen Konstruktoren, Setter (falls vorhanden) und Rechenoperationen.
Rechenoperationen als reine Objektfunktionen
Das Arbeitsblatt verlangt, dass Operationen neue Instanzen erzeugen. Das entspricht einem immutablen Stil und hat Vorteile:
- weniger Seiteneffekte
- einfachere Tests
- sichere Wiederverwendung alter Werte
Beispielprinzip:
GanzeZahl c = a.addiere(b);
a und b bleiben unveraendert.
ggT und selbstKuerzen robust implementieren
Die ggT-Berechnung sollte mit Absolutwerten arbeiten, um Vorzeichenprobleme zu vermeiden.
private int ggT(int a, int b) {
if (a < 0) a *= -1;
if (b < 0) b *= -1;
if (b == 0) return a;
return ggT(b, a % b);
}
Danach kann selbstKuerzen() Zaehler und Nenner durch den ggT teilen.
Sonderfall Division durch Null
Das Blatt legt fest: Falls kein multiplikatives Inverses existiert, gibt die Methode null zurueck. Didaktisch ist das nachvollziehbar, im produktiven Stil kann alternativ eine Exception passender sein.
Vergleich:
null-Rueckgabe Vorteil: einfach Nachteil: Aufrufer muss immer pruefen- Exception Vorteil: Fehler klar signalisiert Nachteil: erfordert Fehlerbehandlung
Fuer das Projekt sollte eine Variante konsistent in allen Klassen genutzt werden.
Konstruktorverkettung im Projekt
Das Arbeitsblatt fordert mehrfach, dass zusaetzliche Konstruktoren auf den Hauptkonstruktor aufrufen. Das verhindert Inkonsistenz.
Beispiel:
public GanzeZahl(NatuerlicheZahl betrag, boolean nichtNegativ) {
this(betrag.getWert() * (nichtNegativ ? 1 : -1));
}
So sind Vorzeichen- und Nullregeln zentral gebuendelt.
toString als Testhilfe
Die Stringdarstellung ist nicht nur Komfortfunktion. Sie ist ein schneller Integritaetscheck:
GanzeZahl(-3)->"-3"RationaleZahl(2, 3)->"2/3"RationaleZahl(4, 6)nach Kuerzung ->"2/3"
Saubere toString-Methoden erleichtern Vergleichstests erheblich.
Testkatalog fuer das komplette Projekt
NatuerlicheZahl
- Konstruktion mit positivem Wert
- Konstruktion mit
0 - Konstruktion mit negativem Wert
GanzeZahl
- positive, negative und null-Werte
- additives Inverses
- Addition/Subtraktion/Multiplikation
RationaleZahl
- Nenner
0 - negativer Nenner
- Kuerzen
- Division und Inverses
- Nullfall bei multiplikativem Inversen
Dieser Katalog deckt die Kernforderungen des Arbeitsblatts ab.
Modellierungsfragen aus dem Blatt didaktisch nutzen
Die abschliessenden Theoriefragen sind ein grosser Mehrwert:
- Unterschied zwischen nicht-negativ und positiv
- Warum private Konstruktoren in bestimmten Klassen sinnvoll sind
- Warum mathematische Teilmengen nicht automatisch Vererbung bedeuten
- Warum kovariante Rueckgabetypen erlaubt sein koennen
Diese Fragen verbinden Implementierung mit Architekturdenken.
Mini-Erweiterung fuer Fortgeschrittene
Ergaenze RationaleZahl um:
compareToequals/hashCode- statische Parse-Methode aus
String
Damit wird das Projekt von einer Uebungsreihe zu einem kleinen, nutzbaren Zahlenmodul.
Bewertungsraster fuer Projektabgabe
- Invarianten eingehalten
- Konstruktorverkettung korrekt
- Rechenmethoden liefern neue Instanzen
- Sonderfaelle behandelt
- Tests nachvollziehbar dokumentiert
Transfer in weitere OOP-Projekte
Das Zahlenprojekt trainiert ein universelles Muster:
- Fachobjekte statt primitive Streuvariablen
- klare Invarianten
- Interface-basierte Faehigkeiten
- testbare Rechenlogik
Genau dieses Muster ist spaeter in vielen Domainen nutzbar, etwa Geometrie, Finanzberechnung oder Datenanalyse.
Abschluss der Vertiefung
Das Arbeitsblatt ist ein Schluesselprojekt im Java-Teil: Es zwingt dazu, OOP-Grundlagen, Typregeln und mathematische Logik in einem konsistenten Modell zusammenzufuehren. Wer dieses Projekt sauber fertigstellt, hat den Uebergang von isolierten Einzeluebungen zu echter Klassenarchitektur geschafft.
Abschlussprojekt und Abnahme
Fuer die finale Abgabe des Zahlenprojekts sollten folgende Nachweise vorhanden sein:
- Klassen
NatuerlicheZahl,GanzeZahl,RationaleZahlvollstaendig implementiert - Invarianten schriftlich dokumentiert
- Testfaelle fuer Normal- und Randbedingungen
- Beispielrechnungen in einer
mainoder in Unit-Tests - kurze Designbegruendung zu Interfaces und Rueckgabetypen
Abnahmefrage:
- Kann jede Rechenmethode ohne Seiteneffekte mehrfach aufgerufen werden und liefert konsistente Ergebnisse?
Wenn diese Frage mit nachvollziehbaren Tests beantwortet ist, ist der Projektkern erreicht.
Pruefungsnahe Kurzfragen
- Warum sollte der Nenner einer rationalen Zahl nie
0sein? - Welche Invarianten muessen bei
GanzeZahlgelten? - Warum sind neue Rueckgabeobjekte oft robuster als Seiteneffekte?
- In welchem Fall liefert das multiplikative Inverse keinen gueltigen Wert?
Mit diesen Fragen wird das Projektwissen von Implementierung auf Begruendungsebene gehoben.
Weiterfuehrende Praxisaufgabe
Erweitere das Zahlenprojekt um eine kleine Konsolenanwendung, in der der Nutzer zwei Zahlenobjekte eingibt und eine Operation waehlt.
Pflichtpunkte:
- Eingaben validieren
- Sonderfaelle (
Nenner 0, Division durch 0) sichtbar behandeln - Ergebnis in normalisierter Form ausgeben
- mindestens zehn reproduzierbare Testfaelle dokumentieren
Damit wird aus dem Klassenprojekt ein kleines, vollstaendig testbares Anwendungsszenario.
Mini-Reflexion
Das Zahlenprojekt ist besonders wertvoll, weil es Regeln, Typen und Rechenlogik gleichzeitig fordert. Genau diese Kombination macht den Unterschied zwischen lauffaehigem Code und stabiler Modellierung.
