Autor: HM_JDSZielgruppe: Sek I

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

Abbildung 1

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

Abbildung 1

GanzeZahl wird im Blatt ueber Vorzeichen + Betrag modelliert.

  • vorzeichen in {-1, 0, 1}
  • betrag als NatuerlicheZahl

Damit wird nicht nur gerechnet, sondern ein internes Modell mit klaren Regeln aufgebaut.

Konstruktionsregeln laut Blatt

  1. Ein zentraler Konstruktor mit int ist die Hauptlogik.
  2. Andere Konstruktoren sollen darauf aufbauen.
  3. NULL und EINS als 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

Abbildung 1

Abbildung 2

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/4 wird intern zu 1/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:

  • AdditivInvertierbar
  • MultiplikativInvertierbar
  • ZahlMitVorzeichen

Didaktischer Kern:

  • gemeinsame Faehigkeiten als Vertrag definieren
  • konkrete Umsetzung in Klassen spezifizieren

Beispielidee:

  • additives Inverses von x ist -x
  • multiplikatives Inverses von x ist 1/x (ausser x == 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:

  • addiere
  • subtrahiere
  • multipliziere
  • dividiere

Regel:

  • Jede Methode liefert eine neue Instanz.
  • subtrahiere soll additivesInverses nutzen.
  • dividiere soll multiplikativesInverses nutzen.

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

Abbildung 1

Abbildung 2

Abbildung 3

Das Arbeitsblatt fordert explizit Testrechnungen in main. Sinnvoll ist eine strukturierte Matrix:

  1. Konstruktor-Randfaelle -5 bei NatuerlicheZahl, Nenner 0, Nenner negativ
  2. Inversen additiv und multiplikativ inkl. Sonderfall 0
  3. Rechenoperationen Addition, Multiplikation, Subtraktion, Division
  4. 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

  1. Invarianten nur teilweise geprueft z. B. Nennerregel bei einem Konstruktor vergessen.
  2. Rechenmethoden veraendern this Fuehrt zu schwer testbaren Seiteneffekten.
  3. ggT ohne Absolutwertbehandlung Negativwerte koennen kuerzen stoeren.
  4. Uneinheitliche Vorzeichenlogik Vorzeichen mal im Zaehler, mal im Nenner.
  5. null-Rueckgaben unkontrolliert verwendet Bei Division durch 0 muss der Aufrufer null pruefen.

Vertiefungsaufgaben

  1. Fuege zu allen Klassen equals und hashCode hinzu.
  2. Ergaenze Vergleichsmethoden (istKleinerAls, compareTo).
  3. Implementiere Parser aus String ("-3/7").
  4. Ersetze null bei fehlendem Inversen durch Exception oder Optional und diskutiere Vor-/Nachteile.
  5. 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

  1. wert >= 0
  2. NULL und EINS verweisen auf gueltige Objekte

GanzeZahl

  1. vorzeichen ist nur -1, 0 oder 1
  2. betrag ist nicht null
  3. Bei Wert 0 gilt Vorzeichen ebenfalls 0

RationaleZahl

  1. nenner > 0
  2. nenner != 0
  3. Bruch liegt gekuerzt vor
  4. 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:

  1. null-Rueckgabe Vorteil: einfach Nachteil: Aufrufer muss immer pruefen
  2. 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

  1. Konstruktion mit positivem Wert
  2. Konstruktion mit 0
  3. Konstruktion mit negativem Wert

GanzeZahl

  1. positive, negative und null-Werte
  2. additives Inverses
  3. Addition/Subtraktion/Multiplikation

RationaleZahl

  1. Nenner 0
  2. negativer Nenner
  3. Kuerzen
  4. Division und Inverses
  5. 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:

  1. compareTo
  2. equals/hashCode
  3. statische Parse-Methode aus String

Damit wird das Projekt von einer Uebungsreihe zu einem kleinen, nutzbaren Zahlenmodul.

Bewertungsraster fuer Projektabgabe

  1. Invarianten eingehalten
  2. Konstruktorverkettung korrekt
  3. Rechenmethoden liefern neue Instanzen
  4. Sonderfaelle behandelt
  5. 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:

  1. Klassen NatuerlicheZahl, GanzeZahl, RationaleZahl vollstaendig implementiert
  2. Invarianten schriftlich dokumentiert
  3. Testfaelle fuer Normal- und Randbedingungen
  4. Beispielrechnungen in einer main oder in Unit-Tests
  5. 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

  1. Warum sollte der Nenner einer rationalen Zahl nie 0 sein?
  2. Welche Invarianten muessen bei GanzeZahl gelten?
  3. Warum sind neue Rueckgabeobjekte oft robuster als Seiteneffekte?
  4. 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:

  1. Eingaben validieren
  2. Sonderfaelle (Nenner 0, Division durch 0) sichtbar behandeln
  3. Ergebnis in normalisierter Form ausgeben
  4. 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.