Der Onlineshop
AFB I–IIEin Onlineshop verkauft Waren mit unterschiedlichen Steuersätzen. Die Preislogik wurde mit Vererbung modelliert.
- - bezeichnung: Zeichenkette
- # nettopreis: Fließkommazahl
- c Artikel(bez: Zeichenkette, netto: Fließkommazahl)
- + getPreis(): Fließkommazahl
- - mhd: Zeichenkette
- c Lebensmittel(bez: Zeichenkette, netto: Fließkommazahl, mhd: Zeichenkette)
- + getPreis(): Fließkommazahl
- c Gutschein(wert: Fließkommazahl)
- + getPreis(): Fließkommazahl
Ein Artikel kostet regulär den Nettopreis plus 19 % Mehrwertsteuer, Lebensmittel plus 7 %. Gutscheine sind steuerfrei. Der Warenkorb kennt beliebig viele Artikel (- artikel: DynArray<Artikel>) und berechnet mit gesamtpreis() die Summe aller Preise.
- Beschreiben Sie die im Klassendiagramm dargestellten Beziehungen. Gehen Sie auf die Bedeutung von
#und auf das mehrfache Auftreten vongetPreis()ein. 4 BE - Implementieren Sie die Klasse
Lebensmittelvollständig in Java. 5 BE - Wenden Sie die Methode
gesamtpreis()auf einen Warenkorb an, der einenArtikel("Kopfhörer", 50.00), einLebensmittel("Kaffee", 10.00, "2027-03-01")und einenGutschein(20.00)enthält. Geben Sie für jeden Schleifendurchlauf die aufgerufene Methode und das Zwischenergebnis an. 4 BE - Erläutern Sie, warum
gesamtpreis()nicht geändert werden muss, wenn der Shop später eine KlasseBuch(ebenfalls 7 %) ergänzt. 4 BE
Summe: 17 BE
Hinweise
Hinweis zu Aufgabe a)
nettopreis lesen?Hinweis zu Aufgabe b)
extends, Konstruktor mit super(…) als erster Anweisung, dann getPreis() überschreiben.Hinweis zu Aufgabe c)
getPreis() läuft, entscheidet das Objekt im jeweiligen Listenplatz.Hinweis zu Aufgabe d)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Lebensmittel und Gutschein sind Unterklassen von Artikel (ein Lebensmittel ist ein Artikel). Sie erben bezeichnung, nettopreis und getPreis(); Lebensmittel ergänzt mhd. # bedeutet protected: nettopreis ist für die Unterklassen direkt zugreifbar, für andere Klassen nicht. getPreis() steht in allen drei Klassen: Die Unterklassen überschreiben die Methode der Oberklasse, weil sie mit anderem Steuersatz rechnen.
Erwartungshorizont zu Aufgabe b)
public class Lebensmittel extends Artikel {
private String mhd;
public Lebensmittel(String bezeichnung, double nettopreis, String mhd) {
super(bezeichnung, nettopreis);
this.mhd = mhd;
}
@Override
public double getPreis() {
return nettopreis * 1.07;
}
}Bewertet: extends (1 BE), Konstruktor mit super an erster Stelle (2 BE), überschreibende Methode mit gleichem Kopf (2 BE). @Override ist empfohlen, aber nicht bewertungsrelevant.
Erwartungshorizont zu Aufgabe c)
| i | Objekt | aufgerufen | Preis | summe |
|---|---|---|---|---|
| 0 | Artikel | Artikel.getPreis() | 59,50 | 59,50 |
| 1 | Lebensmittel | Lebensmittel.getPreis() | 10,70 | 70,20 |
| 2 | Gutschein | Gutschein.getPreis() | 20,00 | 90,20 |
Rückgabe: 90,20 €. Obwohl die Liste den Typ Artikel hat, läuft jeweils die Methode der Klasse des Objekts (dynamische Bindung).
Erwartungshorizont zu Aufgabe d)
Die Schleife ruft für jedes Element nur getPreis() über den Typ Artikel auf. Ein Buch als Unterklasse von Artikel darf in die Liste eingefügt werden und bringt seine eigene überschreibende getPreis() mit; welche Methode läuft, wird erst zur Laufzeit am Objekt entschieden. Warenkorb muss die konkreten Unterklassen also gar nicht kennen — neue Artikelarten erweitern das Programm, ohne vorhandenen Code zu ändern (Polymorphie). Ohne Vererbung bräuchte man für jede Art eine eigene Liste oder eine Fallunterscheidung im Warenkorb.
Figuren im Rollenspiel
AFB II–IIIIn einem Rollenspiel kämpfen Figuren gegeneinander. Jede Figur hat Energie; treffen zieht dem Ziel so viel Energie ab, wie der Angreifer Schaden macht. Die Klassen liegen in einem gemeinsamen Paket.
public class Figur {
private String name;
protected int energie;
public Figur(String name, int energie) { this.name = name; this.energie = energie; }
public int schaden() { return 10; }
public void treffen(Figur ziel) { ziel.energie = ziel.energie - schaden(); }
public int getEnergie() { return energie; }
}
public class Ritter extends Figur {
private int ruestung;
public Ritter(String name, int ruestung) { super(name, 100); this.ruestung = ruestung; }
@Override public int schaden() { return super.schaden() + 5; }
}
public class Magier extends Figur {
public Magier(String name) { super(name, 60); }
@Override public int schaden() { return 2 * super.schaden(); }
@Override public void treffen(Figur ziel) { super.treffen(ziel); energie = energie - 5; }
}- Analysieren Sie den folgenden Ablauf. Geben Sie nach jeder Zeile die Energie von
rundman und begründen Sie jeweils, welche Methoden aufgerufen werden.Ritter r = new Ritter("Rolf", 3); Magier m = new Magier("Mia"); Figur f = m; r.treffen(m); f.treffen(r); m.treffen(m);6 BE - Entwerfen Sie eine Klasse
Heiler, deren Objekte keinen Schaden anrichten, aber eine andere Figur um 15 Energiepunkte heilen können. Stellen Sie die Klasse im Klassendiagramm mit ihrer Beziehung zuFigurdar und implementieren Sie die neue Methode. 5 BE - Implementieren Sie eine Methode
static Figur staerkste(ArrayList<Figur> figuren), die die Figur mit der größten Energie liefert, bei leerer Listenull. 4 BE - Beurteilen Sie den Vorschlag eines Teammitglieds, eine neue Klasse
Dracheals Unterklasse vonRitterzu modellieren, „weil ein Drache genauso viel Schaden macht und man so Code spart“. 4 BE
Summe: 19 BE
Hinweise
Hinweis zu Aufgabe a)
treffen ruft intern schaden() auf — und zwar die Methode des Objekts, an dem treffen läuft. Beim letzten Aufruf sind Angreifer und Ziel dasselbe Objekt.Hinweis zu Aufgabe b)
schaden() überschreiben, heilen(ziel: Figur) ergänzen; energie ist protected.Hinweis zu Aufgabe c)
Hinweis zu Aufgabe d)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
| Zeile | r.energie | m.energie | Begründung |
|---|---|---|---|
| Konstruktoren | 100 | 60 | Startwerte über super(name, …) |
r.treffen(m) | 100 | 45 | geerbtes treffen, darin Ritter.schaden() = 10 + 5 |
f.treffen(r) | 80 | 40 | f zeigt auf den Magier: Magier.treffen → super.treffen mit Magier.schaden() = 20, danach verliert der Magier 5 |
m.treffen(m) | 80 | 15 | Ziel ist der Magier selbst: 40 − 20 = 20, dann − 5 = 15 |
Entscheidend: f hat den Typ Figur, das Objekt ist aber ein Magier — es läuft seine überschreibende Methode. Innerhalb von Figur.treffen wird schaden() ebenfalls dynamisch gebunden.
Erwartungshorizont zu Aufgabe b)
- - name: Zeichenkette
- # energie: Ganzzahl
- + schaden(): Ganzzahl
- + treffen(ziel: Figur)
- c Heiler(name: Zeichenkette)
- + schaden(): Ganzzahl
- + heilen(ziel: Figur)
public class Heiler extends Figur {
public Heiler(String name) {
super(name, 70);
}
@Override
public int schaden() {
return 0;
}
public void heilen(Figur ziel) {
ziel.energie = ziel.energie + 15;
}
}Der Startwert 70 ist frei gewählt. Weil energie protected ist und alle Klassen im selben Paket liegen, darf heilen die Energie des Ziels ändern; sauberer wäre eine Methode erhoeheEnergie(n: Ganzzahl) in Figur.
Erwartungshorizont zu Aufgabe c)
static Figur staerkste(ArrayList<Figur> figuren) {
Figur beste = null;
for (int i = 0; i < figuren.size(); i++) {
if (beste == null || figuren.get(i).getEnergie() > beste.getEnergie()) {
beste = figuren.get(i);
}
}
return beste;
}Für die Liste aus a) (Magier, Ritter) liefert die Methode den Ritter. Die Bedingung beste == null behandelt das erste Element; bei leerer Liste bleibt beste gleich null.
Erwartungshorizont zu Aufgabe d)
Der Vorschlag ist abzulehnen. „Ein Drache ist ein Ritter“ ist fachlich falsch; der Drache würde auch ruestung und alles, was Ritter künftig bekommt (z. B. ein Pferd), erben. Ändert jemand den Ritter-Schaden, ändert sich unbeabsichtigt auch der Drache. Gleicher Schaden ist kein Grund für Vererbung, sondern Zufall der aktuellen Werte. Besser: Drache als eigene Unterklasse von Figur mit eigener schaden()-Methode; gemeinsamer Code gehört in die gemeinsame Oberklasse Figur.
