Termine im digitalen Schulkalender
AFB I–IIFür den digitalen Schulkalender wird eine Klasse Termin entwickelt. Ein Termin besteht aus einem Titel, einem Tag (1 bis 31), einem Monat (1 bis 12), der Stunde des Beginns (0 bis 23) und einer Dauer in Minuten. Die Dauer muss zwischen 15 und 480 Minuten liegen und ein Vielfaches von 15 sein, weil der Kalender in Viertelstunden-Rastern arbeitet. Vereinfachend wird nicht geprüft, ob ein Monat wirklich 31 Tage hat.
Alle Setter übernehmen einen Wert nur, wenn er gültig ist; andernfalls bleibt der bisherige Wert erhalten. Der Konstruktor belegt zunächst Tag und Monat mit 1, die Stunde mit 8 und die Dauer mit 60 und übernimmt anschließend die übergebenen Werte über die Setter – ungültige Parameter führen also zu den Standardwerten.
- - titel: Zeichenkette
- - tag: Ganzzahl
- - monat: Ganzzahl
- - stunde: Ganzzahl
- - dauer: Ganzzahl
- c Termin(titel: Zeichenkette, tag: Ganzzahl, monat: Ganzzahl, stunde: Ganzzahl)
- + getTitel(): Zeichenkette
- + getTag(): Ganzzahl
- + setTag(tag: Ganzzahl)
- + getMonat(): Ganzzahl
- + setMonat(monat: Ganzzahl)
- + getStunde(): Ganzzahl
- + setStunde(stunde: Ganzzahl)
- + getDauer(): Ganzzahl
- + setDauer(dauer: Ganzzahl)
public class Termin {
private String titel;
private int tag;
private int monat;
private int stunde;
private int dauer;
public String getTitel() {
return titel;
}
public int getTag() {
return tag;
}
public void setTag(int tag) {
if (tag >= 1 && tag <= 31) {
this.tag = tag;
}
}
public int getMonat() {
return monat;
}
public void setMonat(int monat) {
if (monat >= 1 && monat <= 12) {
this.monat = monat;
}
}
public int getStunde() {
return stunde;
}
// Konstruktor, setStunde, getDauer und setDauer fehlen
}
Termin t = new Termin("Zahnarzt", 31, 13, 9);
t.setDauer(50);
t.setStunde(24);
t.setDauer(90);
t.setTag(0);
- Erläutern Sie das Prinzip der Datenkapselung am Beispiel der Klasse
Termin. Gehen Sie dabei auf einen konkreten Fehler ein, der ohne Kapselung entstehen könnte. - Ergänzen Sie die Klasse aus Material 1 um den Konstruktor sowie die Methoden
setStunde,getDauerundsetDauergemäß der Beschreibung. - Geben Sie den Zustand des Objekts
tnach Ausführung der Aufruffolge in Material 2 als Objektkarte an.
Hinweise
Hinweis zu Aufgabe a)
private für andere Klassen? Wie erreichen sie die Werte trotzdem? Stell dir vor, die Kalender-App könnte direkt t.stunde = 25; schreiben – was würde die Wochenansicht dann anzeigen?„Erläutern“ heißt: das Prinzip nicht nur definieren, sondern an diesem Beispiel verständlich machen.Hinweis zu Aufgabe b)
this. „Vielfaches von 15“ prüfst du mit dem Rest der Division: dauer % 15 == 0. Im Konstruktor erst die Standardwerte setzen, dann die Setter aufrufen.„Ergänzen“ heißt: die vorhandene Implementierung weiterführen, ohne sie zu verändern.Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Bei der Datenkapselung sind alle Attribute private; andere Klassen können sie weder lesen noch verändern. Der Zugriff erfolgt ausschließlich über öffentliche Methoden: Getter liefern den Wert (getStunde()), Setter ändern ihn, prüfen dabei aber, ob der neue Wert gültig ist (setStunde akzeptiert nur 0 bis 23).
Beispiel: Wäre stunde öffentlich, könnte die App t.stunde = 25; ausführen. Der Termin läge dann auf einer nicht existierenden Uhrzeit, die Tagesansicht würde ihn nicht oder falsch anzeigen. Durch die Kapselung bleibt jedes Termin-Objekt stets in einem gültigen Zustand; die Verantwortung dafür liegt an einer einzigen Stelle, der Klasse selbst. Zudem kann die interne Speicherung später geändert werden (z. B. Beginn in Minuten seit Mitternacht), ohne dass andere Klassen angepasst werden müssen.
Erwartungshorizont zu Aufgabe b)
public Termin(String titel, int tag, int monat, int stunde) {
this.titel = titel;
this.tag = 1;
this.monat = 1;
this.stunde = 8;
this.dauer = 60;
setTag(tag);
setMonat(monat);
setStunde(stunde);
}
public void setStunde(int stunde) {
if (stunde >= 0 && stunde <= 23) {
this.stunde = stunde;
}
}
public int getDauer() {
return dauer;
}
public void setDauer(int dauer) {
if (dauer >= 15 && dauer <= 480 && dauer % 15 == 0) {
this.dauer = dauer;
}
}
Bewertet werden: Konstruktor ohne Rückgabetyp, Standardwerte vor den Setter-Aufrufen, Nutzung der Setter statt direkter Zuweisung ungeprüfter Werte, this bei gleichnamigen Parametern, vollständige Prüfung der drei Bedingungen für die Dauer, return im Getter.
Erwartungshorizont zu Aufgabe c)
- titel = "Zahnarzt"
- tag = 31
- monat = 1
- stunde = 9
- dauer = 90
Begründung der Werte: Tag 31 ist gültig; Monat 13 ist ungültig, daher bleibt der Standardwert 1. setDauer(50) scheitert (kein Vielfaches von 15), setStunde(24) scheitert (größer als 23), setDauer(90) gelingt, setTag(0) scheitert – der Tag bleibt 31.
Abrechnung mit dem Stromzähler
AFB II–IIIEine Hausverwaltung erfasst den Stromverbrauch ihrer Wohnungen mit einer Software. Jeder Stromzähler hat eine feste Zählernummer und einen Zählerstand in Kilowattstunden (kWh), der im realen Gerät nur steigen kann. Bei jeder Jahresablesung wird der aktuelle Stand als „letzte Ablesung“ gespeichert; abgerechnet wird der Verbrauch seit der letzten Ablesung multipliziert mit dem Preis pro kWh. Der Preis kann sich durch einen neuen Stromtarif ändern.
Die erste Version der Klasse (Material 3) wurde ohne Datenkapselung programmiert. In der Hausverwaltungs-App fiel ein Kunde mit Stromkosten von 0,00 Euro auf; der zugehörige Programmausschnitt ist in Material 4 abgebildet.
public class Stromzaehler {
public String zaehlernummer;
public double stand;
public double letzteAblesung;
public double preisProKWh;
public Stromzaehler(String zaehlernummer, double preisProKWh) {
this.zaehlernummer = zaehlernummer;
this.preisProKWh = preisProKWh;
stand = 0.0;
letzteAblesung = 0.0;
}
public void verbrauchen(double kwh) {
stand = stand + kwh;
}
public double getKosten() {
return (stand - letzteAblesung) * preisProKWh;
}
public void ablesen() {
letzteAblesung = stand;
}
}
Stromzaehler z = new Stromzaehler("1ESY-4471", 0.32);
z.verbrauchen(1800.0);
z.stand = z.stand - 600.0; // "Korrektur" nach Zählertausch
z.verbrauchen(-50.0); // Tippfehler bei der Eingabe
z.preisProKWh = 0.0; // Testwert, nicht entfernt
System.out.println(z.getKosten());
- Analysieren Sie Material 3 und Material 4: Ermitteln Sie die Ausgabe des Programmausschnitts und arbeiten Sie heraus, welche ungültigen Zustände entstehen und welche Eigenschaften der Klasse sie ermöglichen.
- Entwerfen Sie eine Klassenkarte für eine gekapselte Version der Klasse
Stromzaehler. Entscheiden Sie für jedes Attribut begründet, ob es einen Getter, einen Setter mit Prüfung oder keinen Zugriff von außen erhält. Berücksichtigen Sie, dass bei einem Zählertausch der neue Zähler mit einem von 0 verschiedenen Anfangsstand in Betrieb gehen kann. - Implementieren Sie Ihre gekapselte Klasse in Java. Zusätzlich soll eine Anfrage
getVerbrauchSeitAblesung()vorhanden sein; als plausibel gilt ein Preis größer als 0 und kleiner als 2,00 Euro pro kWh. - Der Netzbetreiber fordert, eine Methode
setStand(neuerStand: Fließkommazahl)aufzunehmen, damit fehlerhafte Zählerstände nachträglich korrigiert werden können. Beurteilen Sie diese Forderung.
Hinweise
Hinweis zu Aufgabe a)
stand, letzteAblesung und preisProKWh Zeile für Zeile. Für jede verdächtige Zeile: Welche Regel aus dem Text wird verletzt – und was an Material 3 macht das möglich (Sichtbarkeit, fehlende Prüfung)?„Analysieren“ heißt: unter der Fragestellung „Wie entsteht der Fehler?“ die entscheidenden Stellen herausarbeiten.Hinweis zu Aufgabe b)
Hinweis zu Aufgabe c)
private. verbrauchen nimmt nur positive Werte an. Ruft der Konstruktor den Setter für den Preis auf, brauchst du vorher einen gültigen Startwert. getKosten() kann getVerbrauchSeitAblesung() nutzen.Hinweis zu Aufgabe d)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Ablauf: stand = 1800.0 → 1200.0 (direkte Zuweisung) → 1150.0 (negativer Verbrauch). Preis = 0.0. Ausgabe: 0.0, denn (1150,0 − 0,0) · 0,0 = 0.
Ungültige Zustände: Der Zählerstand sinkt zweimal, obwohl er real nur steigen kann (600 kWh werden durch direkte Zuweisung, 50 kWh durch einen negativen Verbrauch „verschenkt“). Der Preis 0,00 Euro ist nicht plausibel; die Kosten sind falsch. Würde stand unter letzteAblesung gesetzt, entstünden sogar negative Kosten.
Ursachen in der Klasse: Alle Attribute sind public und können von jedem Programmteil ohne Prüfung überschrieben werden (auch zaehlernummer). verbrauchen prüft den Parameter nicht, und der Konstruktor übernimmt jeden Preis ungeprüft. Die Klasse kann so nicht sicherstellen, dass ihr Zustand gültig bleibt.
Erwartungshorizont zu Aufgabe b)
- - zaehlernummer: Zeichenkette
- - stand: Fließkommazahl
- - letzteAblesung: Fließkommazahl
- - preisProKWh: Fließkommazahl
- c Stromzaehler(zaehlernummer: Zeichenkette, anfangsstand: Fließkommazahl, preisProKWh: Fließkommazahl)
- + getZaehlernummer(): Zeichenkette
- + getStand(): Fließkommazahl
- + getPreisProKWh(): Fließkommazahl
- + setPreisProKWh(preis: Fließkommazahl)
- + verbrauchen(kwh: Fließkommazahl)
- + ablesen()
- + getVerbrauchSeitAblesung(): Fließkommazahl
- + getKosten(): Fließkommazahl
zaehlernummer: nur Getter – die Nummer ist fest. stand: nur Getter; verändert wird er ausschließlich über verbrauchen (nur positive Werte), der Anfangsstand kommt über den Konstruktor. letzteAblesung: kein Zugriff von außen; gesetzt nur durch ablesen(), genutzt über getVerbrauchSeitAblesung(). preisProKWh: Getter und Setter mit Plausibilitätsprüfung, weil sich Tarife ändern. Ein Getter für letzteAblesung ist ebenfalls vertretbar.
Erwartungshorizont zu Aufgabe c)
public class Stromzaehler {
private String zaehlernummer;
private double stand;
private double letzteAblesung;
private double preisProKWh;
public Stromzaehler(String zaehlernummer, double anfangsstand, double preisProKWh) {
this.zaehlernummer = zaehlernummer;
this.stand = 0.0;
if (anfangsstand > 0) {
this.stand = anfangsstand;
}
this.letzteAblesung = this.stand;
this.preisProKWh = 0.30;
setPreisProKWh(preisProKWh);
}
public String getZaehlernummer() {
return zaehlernummer;
}
public double getStand() {
return stand;
}
public double getPreisProKWh() {
return preisProKWh;
}
public void setPreisProKWh(double preis) {
if (preis > 0 && preis < 2.0) {
preisProKWh = preis;
}
}
public void verbrauchen(double kwh) {
if (kwh > 0) {
stand = stand + kwh;
}
}
public double getVerbrauchSeitAblesung() {
return stand - letzteAblesung;
}
public double getKosten() {
return getVerbrauchSeitAblesung() * preisProKWh;
}
public void ablesen() {
letzteAblesung = stand;
}
}
Wesentlich: private Attribute, Prüfung in verbrauchen und setPreisProKWh, gültiger Startpreis vor dem Setter-Aufruf (hier 0,30 Euro als Standardwert; andere Lösungen, die einen ungültigen Preis sicher abfangen, sind zu akzeptieren), letzteAblesung startet beim Anfangsstand, damit der Altverbrauch nicht abgerechnet wird. Mit dieser Klasse wären die Zeilen 3 und 5 aus Material 4 nicht übersetzbar, Zeile 4 bliebe wirkungslos; der Konstruktoraufruf in Zeile 1 erhält zusätzlich den Anfangsstand.
Erwartungshorizont zu Aufgabe d)
Für die Forderung: Ablesefehler oder Übertragungsfehler kommen vor und müssen korrigiert werden können; ohne Setter bliebe ein falscher Stand dauerhaft im System.
Gegen die Forderung: Ein öffentlicher, ungeprüfter setStand hebt die Kapselung praktisch auf: Genau die Manipulation aus Material 4 wäre wieder möglich, und der Zählerstand könnte sinken, was dem realen Gerät widerspricht. Der Zählertausch – der wichtigste Anlass – ist bereits über den Konstruktor mit Anfangsstand gelöst: Ein neuer Zähler ist ein neues Objekt.
Mögliches Urteil: Die Forderung ist in dieser Form abzulehnen. Falls Korrekturen nötig sind, sollte es höchstens eine eingeschränkte Methode geben (z. B. korrigieren(neuerStand), die nur Werte ≥ letzteAblesung akzeptiert und den Vorgang protokolliert). Ein begründetes Urteil für einen Setter mit strenger Prüfung ist ebenfalls vollständig, wenn die Gefahren benannt und abgewogen werden.
