MINT lernen

Abituraufgaben: Objekte, die Objekte kennen

Züge, die ihre Lok kennen, und Kunden, die einen E-Scooter leihen: Beziehungen herstellen, nutzen und wieder lösen.

Dein Fortschritt:
0 / 0 Aufgaben
1

Lokwechsel im Bahnhof

AFB I–II

Ein Eisenbahnunternehmen plant den Einsatz seiner Lokomotiven mit einer Software. Ein Zug wird zunächst ohne Lok bereitgestellt und bekommt mit ankuppeln eine Lok zugeordnet. Die Höchstgeschwindigkeit eines Zuges richtet sich nach seiner Lok. Material 1 zeigt das Klassendiagramm, den Quelltext der Klasse Zug und eine Anweisungsfolge aus dem Betrieb.

Material 1: Zug und Lok
Zug
  • - nummer: Zeichenkette
  • - lok: Lok
  • c Zug(nummer: Zeichenkette)
  • + ankuppeln(lok: Lok)
  • + getLok(): Lok
  • + getHoechsttempo(): Ganzzahl
kennt0..1
Lok
  • - baureihe: Zeichenkette
  • - hoechstgeschwindigkeit: Ganzzahl
  • c Lok(baureihe: Zeichenkette, hoechstgeschwindigkeit: Ganzzahl)
  • + getBaureihe(): Zeichenkette
  • + getHoechstgeschwindigkeit(): Ganzzahl
public class Zug {
    private String nummer;
    private Lok lok;

    public Zug(String nummer) {
        this.nummer = nummer;
        lok = null;
    }

    public void ankuppeln(Lok lok) {
        this.lok = lok;
    }

    public Lok getLok() {
        return lok;
    }

    public int getHoechsttempo() {
        return lok.getHoechstgeschwindigkeit();
    }
}
Lok l1 = new Lok("BR 146", 160);
Lok l2 = new Lok("BR 218", 140);
Zug re = new Zug("RE 7");
Zug rb = new Zug("RB 42");
Zug ic = new Zug("IC 2036");
re.ankuppeln(l1);
rb.ankuppeln(l1);
re.ankuppeln(l2);
Geschwindigkeiten in km/h.
  1. Beschreiben Sie, wie die Beziehung „Zug kennt Lok“ im Quelltext umgesetzt ist, wie sie hergestellt wird und wie getHoechsttempo sie nutzt.
  2. Zeichnen Sie das Objektdiagramm nach Ausführung der Anweisungsfolge und geben Sie an, welche Werte re.getHoechsttempo() und rb.getHoechsttempo() nun liefern.
  3. Begründen Sie, dass der Aufruf ic.getHoechsttempo() zum Programmabbruch führt, und geben Sie eine abgesicherte Fassung der Methode an, die in diesem Fall 0 liefert.

Hinweise

Hinweis zu Aufgabe a)
Achte auf drei Stellen: den Datentyp des Attributs lok, die Methode ankuppeln und den Punkt in lok.getHoechstgeschwindigkeit().Beschreiben heißt: in Fachsprache wiedergeben, was da steht – Begriffe wie Attribut, Referenz, Parameter, Methodenaufruf verwenden.
Hinweis zu Aufgabe b)
Fünf new – fünf Objektkarten. Arbeite die drei ankuppeln-Aufrufe der Reihe nach ab; ein späterer Aufruf überschreibt die frühere Referenz.
Hinweis zu Aufgabe c)
Welchen Wert hat das Attribut lok bei ic? Was passiert, wenn man über diesen Wert eine Methode aufruft?

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)

Die Klasse Zug besitzt ein privates Attribut lok vom Typ Lok. Es speichert eine Referenz auf ein Lok-Objekt; der Konstruktor setzt es auf null, ein neuer Zug kennt also noch keine Lok. Die Beziehung wird mit dem Auftrag ankuppeln hergestellt: Die übergebene Referenz wird mit this.lok = lok; im Attribut gespeichert (this unterscheidet Attribut und gleichnamigen Parameter). getHoechsttempo berechnet nichts selbst, sondern ruft über die gespeicherte Referenz die Anfrage getHoechstgeschwindigkeit() der Lok auf und gibt deren Ergebnis zurück (Delegation).

Erwartungshorizont zu Aufgabe b)
re: Zug
  • nummer = "RE 7"
  • lok = l2
rb: Zug
  • nummer = "RB 42"
  • lok = l1
ic: Zug
  • nummer = "IC 2036"
  • lok = null
l1: Lok
  • baureihe = "BR 146"
  • hoechstgeschwindigkeit = 160
l2: Lok
  • baureihe = "BR 218"
  • hoechstgeschwindigkeit = 140

Die Referenzen können auch als Pfeile von re zu l2 und von rb zu l1 gezeichnet werden. Zwischenzeitlich kannten re und rb beide l1; nach dem dritten Aufruf verweist re auf l2. re.getHoechsttempo() liefert 140, rb.getHoechsttempo() liefert 160.

Erwartungshorizont zu Aufgabe c)

Für ic wurde ankuppeln nie aufgerufen, das Attribut lok hat daher noch den Wert null aus dem Konstruktor. null verweist auf kein Objekt; der Aufruf lok.getHoechstgeschwindigkeit() kann an kein Objekt gerichtet werden und löst eine NullPointerException aus, das Programm bricht ab.

public int getHoechsttempo() {
    if (lok == null) {
        return 0;
    }
    return lok.getHoechstgeschwindigkeit();
}
2

E-Scooter-Verleih in der Innenstadt

AFB II–III

Ein Stadtwerk verleiht E-Scooter per App. Die Klasse EScooter liegt bereits vor (Material 2). Nun soll die Klasse Kunde entstehen. Ein Kunde hat einen Namen und ein Guthaben in Cent und kann höchstens einen E-Scooter gleichzeitig ausleihen.

Eine Ausleihe gelingt nur, wenn der Scooter existiert und frei ist, der Kunde gerade keinen Scooter hat, sein Guthaben mindestens 100 Cent beträgt und der Akku noch mindestens 20 % geladen ist. Dann merkt sich der Kunde den Scooter, der Scooter gilt als belegt, und die Methode meldet den Erfolg. Bei der Rückgabe wird die Fahrtdauer in Minuten angegeben: Der Scooter verbraucht Akku für die Fahrt, dem Kunden werden 100 Cent Grundgebühr plus 25 Cent je Minute abgezogen, der Scooter ist wieder frei, und der Kunde hat keinen Scooter mehr.

Material 2: Klasse EScooter und Testfolge
EScooter
  • - id: Zeichenkette
  • - akku: Ganzzahl
  • - frei: Wahrheitswert
  • c EScooter(id: Zeichenkette)
  • + getId(): Zeichenkette
  • + getAkku(): Ganzzahl
  • + istFrei(): Wahrheitswert
  • + setFrei(frei: Wahrheitswert)
  • + fahren(minuten: Ganzzahl)
Kunde jo = new Kunde("Jonas", 450);
Kunde ma = new Kunde("Marie", 80);
EScooter s1 = new EScooter("S-11");
EScooter s2 = new EScooter("S-12");
jo.ausleihen(s1);      // (1)
ma.ausleihen(s2);      // (2)
jo.ausleihen(s2);      // (3)
jo.zurueckgeben(12);   // (4)
jo.ausleihen(s1);      // (5)
Ein neuer Scooter hat 100 % Akku und ist frei. fahren(minuten) senkt den Akku um 2 Prozentpunkte je Minute, jedoch nicht unter 0.
  1. Erstellen Sie ein Klassendiagramm der Klasse Kunde mit der Beziehung zur Klasse EScooter einschließlich Beschriftung und Kardinalitäten.
  2. Implementieren Sie die Klasse Kunde mit Konstruktor, den Methoden ausleihen und zurueckgeben sowie den benötigten Gettern.
  3. Erläutern Sie die Testfolge aus Material 2: Geben Sie für (1), (2), (3) und (5) den Rückgabewert an, nennen Sie jeweils die ausschlaggebende Bedingung und stellen Sie die Objekte jo und s1 nach (4) als Objektkarten dar.
  4. Erörtern Sie den Vorschlag, dass zusätzlich jeder E-Scooter den ausleihenden Kunden kennen soll, damit das Stadtwerk bei einem falsch abgestellten Scooter sofort den Verursacher ermitteln kann.

Hinweise

Hinweis zu Aufgabe a)
Die Beziehung wird zum Attribut. Überlege für jede Seite der Linie: Wie viele Scooter hat ein Kunde höchstens gleichzeitig, wie viele Kunden ein Scooter?
Hinweis zu Aufgabe b)
Prüfe in ausleihen alle fünf Bedingungen, bevor du etwas veränderst – und prüfe s == null zuerst. In zurueckgeben brauchst du die Referenz auf den Scooter, bevor du sie auf null setzt.
Hinweis zu Aufgabe c)
Führe über Guthaben, Akku und Belegung Buch. Nach (4): 450 − (100 + 25 · 12) = ?
Hinweis zu Aufgabe d)
Mögliche Gesichtspunkte: Nutzen für das Stadtwerk, Aufwand beim Herstellen und Lösen der Beziehung, Gefahr widersprüchlicher Daten, Datenschutz.

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)
Kunde
  • - name: Zeichenkette
  • - guthaben: Ganzzahl
  • - scooter: EScooter
  • c Kunde(name: Zeichenkette, guthaben: Ganzzahl)
  • + getName(): Zeichenkette
  • + getGuthaben(): Ganzzahl
  • + getScooter(): EScooter
  • + ausleihen(s: EScooter): Wahrheitswert
  • + zurueckgeben(minuten: Ganzzahl)
leiht0..10..1
EScooter
  • (Attribute wie in Material 2)

Ein Kunde leiht höchstens einen Scooter, ein Scooter ist höchstens an einen Kunden verliehen: auf beiden Seiten 0..1. Das Attribut scooter darf entfallen, wenn die Beziehung nur als Linie dargestellt wird. ausleihen muss einen Wahrheitswert liefern, da „die Methode meldet den Erfolg“.

Erwartungshorizont zu Aufgabe b)
public class Kunde {
    private String name;
    private int guthaben;
    private EScooter scooter;

    public Kunde(String name, int guthaben) {
        this.name = name;
        this.guthaben = guthaben;
        scooter = null;
    }

    public String getName() {
        return name;
    }

    public int getGuthaben() {
        return guthaben;
    }

    public EScooter getScooter() {
        return scooter;
    }

    public boolean ausleihen(EScooter s) {
        if (s == null || scooter != null || !s.istFrei()
                || guthaben < 100 || s.getAkku() < 20) {
            return false;
        }
        scooter = s;
        s.setFrei(false);
        return true;
    }

    public void zurueckgeben(int minuten) {
        if (scooter != null) {
            scooter.fahren(minuten);
            guthaben = guthaben - (100 + 25 * minuten);
            scooter.setFrei(true);
            scooter = null;
        }
    }
}

Bewertungsrelevant: Beziehung erst nach vollständiger Prüfung herstellen; null-Prüfung des Parameters vor dessen Nutzung; Zustand des Scooters über dessen Methoden ändern (Delegation), nicht über seine Attribute; beim Zurückgeben zuerst den Scooter bedienen, dann die Referenz lösen.

Erwartungshorizont zu Aufgabe c)

(1) true: s1 ist frei, Akku 100 %, Jonas hat 450 Cent und keinen Scooter. Jonas kennt nun s1, s1 ist belegt.

(2) false: Maries Guthaben von 80 Cent liegt unter 100 Cent.

(3) false: Jonas hat bereits einen Scooter (scooter != null), obwohl s2 frei ist.

(4) Akku: 100 − 2 · 12 = 76; Guthaben: 450 − (100 + 25 · 12) = 50; s1 ist wieder frei, Jonas kennt keinen Scooter mehr.

jo: Kunde
  • name = "Jonas"
  • guthaben = 50
  • scooter = null
s1: EScooter
  • id = "S-11"
  • akku = 76
  • frei = true

(5) false: s1 ist zwar frei und hat genug Akku, aber das Guthaben von 50 Cent reicht nicht.

Erwartungshorizont zu Aufgabe d)

Dafür: Mit einem Attribut kunde: Kunde in EScooter kann man vom Scooter aus direkt den Nutzer erfragen (s.getKunde().getName()), ohne alle Kunden durchsuchen zu müssen. Das beschleunigt die Suche nach dem Verursacher und erlaubt weitere Funktionen, etwa eine Nachricht an den aktuellen Nutzer.

Dagegen: Die Beziehung muss nun an zwei Stellen gepflegt werden. ausleihen und zurueckgeben müssen beide Referenzen setzen bzw. auf null setzen; wird eine vergessen, entstehen widersprüchliche Zustände (der Scooter „kennt“ einen Kunden, der ihn nicht mehr hat). Außerdem kennt der Scooter nach der Rückgabe den Kunden nicht mehr – für einen erst später entdeckten, falsch abgestellten Scooter hilft die Beziehung also gar nicht; dafür bräuchte man eine gespeicherte Ausleihhistorie. Aus Sicht des Datenschutzes sollten personenbezogene Daten nur so lange wie nötig verknüpft sein.

Mögliches Fazit: Die zweiseitige Beziehung löst das genannte Problem nur während der laufenden Fahrt und erhöht die Fehleranfälligkeit. Sinnvoller ist ein eigenes Objekt, das jede abgeschlossene Ausleihe mit Scooter, Kunde und Zeit festhält. Wer die zweiseitige Beziehung dennoch befürwortet, muss begründen, wie die Konsistenz in beiden Klassen sichergestellt wird.