Informatik

Rückblick

In diesem Kapitel ging es nur zum kleineren Teil um neue Sprachmittel. Der größere Teil waren Entwurfsentscheidungen: Wann zieht man Gemeinsames nach oben? Wann genügt eine Zusicherung, wann braucht es eine gemeinsame Oberklasse? Wann ist static richtig und wann ein Warnzeichen?

Genau danach wird in der Qualifikationsphase gefragt – und zwar selten mit „setze um", sondern meistens mit „begründe" und „beurteile".

Das kann ich jetzt

  • Ich kann ein Implementationsdiagramm lesen und in ein Klassengerüst übersetzen – und umgekehrt. (1.1)
  • Ich kenne die Zeichen für die Sichtbarkeiten, die Notation für Beziehungen und weiß, was unterstrichen bedeutet. (1.1, 1.2)
  • Ich unterscheide Objektattribut, Klassenattribut und Konstante und kann jede Wahl begründen. (1.2)
  • Ich entscheide mit dem „ist ein"-Test, ob eine Vererbung oder eine Assoziation richtig ist. (1.3)
  • Ich kann sagen, was beim Generalisieren nach oben gehört und was unten bleibt. (1.3)
  • Ich kann Polymorphie an einem Beispiel erklären und sagen, wann entschieden wird, welche Methode läuft. (1.4)
  • Ich unterscheide statischen und dynamischen Typ und weiß, worüber jeder entscheidet. (1.4)
  • Ich kann begründen, wann eine Klasse abstrakt sein sollte. (1.5)
  • (LK) Ich kann eine Schnittstelle von einer abstrakten Klasse unterscheiden und beide einsetzen. (1.6)

Wo stehe ich?

Geh die Tabelle Zeile für Zeile durch und kreuze ehrlich an. Für jede Zeile, die du nicht ankreuzen kannst, steht rechts die Stelle, an der es steht.

Das kann ich Wenn nicht: hier steht es
Ich schreibe aus einem Diagramm ein Klassengerüst, ohne etwas zu erfinden. 1.1 Vom Diagramm zum Quelltext
Ich zeichne aus einer Klasse ein Diagramm – mit den privaten Methoden. 1.1 Aufgabe 1
Ich weiß, was ein Diagramm grundsätzlich nicht ausdrücken kann. 1.1 Vertiefung 1
Ich erkenne, ob eine Angabe zum Objekt oder zur Klasse gehört. 1.2 Ein Wert für die ganze Klasse
Ich schreibe eine Konstante richtig hin und weiß, was final schützt. 1.2 Konstanten
Ich entscheide zwischen Vererbung und Assoziation. 1.3 Der „ist ein"-Test
Ich weiß, was beim Zusammenziehen nach oben gehört und was nicht. 1.3 Was gehört nach oben?
Ich kann begründen, warum private meist besser ist als protected. 1.3 Wie weit macht man auf?
Ich sage voraus, welche Fassung einer überschriebenen Methode läuft. 1.4 Aufgabe 1
Ich sage voraus, welche Zeilen sich übersetzen lassen und welche nicht. 1.4 Aufgabe 2
Ich kann erklären, warum eine neue Unterklasse keine Änderung erzwingt. 1.4 Der eigentliche Gewinn
Ich weiß, was eine abstrakte Klasse verbietet – und was sie erlaubt. 1.5 Aufgabe 1
Ich begründe, ob eine Oberklasse abstrakt sein sollte. 1.5 Wann abstrakt, wann konkret?
(LK) Ich entscheide zwischen abstrakter Klasse und Schnittstelle. 1.6 Abstrakte Klasse oder Schnittstelle?

Ein bis zwei nicht angekreuzte Zeilen aufzuarbeiten ist ein realistisches Programm für diese Woche. Bei mehr als vier lohnt es sich, mit 1.3 anzufangen – darauf bauen die übrigen auf.

Aufgabe 4 gehört zum Leistungskurs, weil sie Schnittstellen voraussetzt.


Gemischte Aufgaben

Aufgabe 1: Diagramm lesen und schreiben

Ohne Rechner.

classDiagram
    class Form {
        <<abstract>>
        #name: String
        -erzeugt: int$
        +Form(pName: String)
        +gibName() String
        +berechneFlaeche() double*
        +beschreibe() String
        +gibErzeugt() int$
    }
    class Kreis {
        -radius: double
        +Kreis(pRadius: double)
        +berechneFlaeche() double
    }
    class Rechteck {
        -breite: double
        -hoehe: double
        +Rechteck(pBreite: double, pHoehe: double)
        +berechneFlaeche() double
    }
    Form <|-- Kreis
    Form <|-- Rechteck

a) Schreib das Klassengerüst zu Form und Kreis – ohne Methodenrümpfe, nur Köpfe, Sichtbarkeiten und Typen.

b) Warum ist name mit # gekennzeichnet und nicht mit -? Was wäre der Nachteil von -, und was der Nachteil von #?

c) berechneFlaeche ist abstrakt. Was folgt daraus für Form, und was für jede Unterklasse?

d) beschreibe() ist nicht abstrakt, obwohl sie die Fläche nennen soll. Wie kann das funktionieren, wenn Form gar nicht weiß, wie man die Fläche berechnet?

e) erzeugt und gibErzeugt() sind unterstrichen. Was heißt das, und wo im Quelltext wird erzeugt erhöht?

Tipp zu d)

Eine nicht abstrakte Methode in der Oberklasse darf eine abstrakte Methode derselben Klasse aufrufen. Zur Laufzeit steht ja fest, welches Objekt gemeint ist – und damit auch, welche Fassung von berechneFlaeche gilt.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Aufgabe 2: Wer wird aufgerufen?

Ohne Rechner. Quadrat ist dabei eine Unterklasse von Rechteck, und Rechteck erbt von der abstrakten Klasse Form aus Aufgabe 1.

Form[] formen = new Form[3];
formen[0] = new Kreis(2.0);
formen[1] = new Rechteck(3.0, 4.0);
formen[2] = new Quadrat(5.0);
 
for (int i = 0; i < formen.length; i++) {
    IO.println(formen[i].beschreibe());
}

a) Der Feldtyp ist Form, gespeichert sind drei verschiedene Klassen. Warum ist das erlaubt?

b) Welche berechneFlaeche-Fassung läuft in jedem der drei Durchläufe? Wann wird das entschieden?

c) Was passierte, wenn Quadrat die Methode berechneFlaeche nicht überschreibt?

d) Ergänze eine vierte Zeile formen[3] = new Form("etwas");. Nenne zwei Gründe, warum sie nicht übersetzt.

e) Jemand schreibt statt der Schleife eine if-Kette mit instanceof, die für jede Formart eigens rechnet. Nenne zwei Nachteile.

f) Formuliere in einem Satz, was du mit Polymorphie gewinnst – gemessen an dem, was du ohne sie schreiben müsstest.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Aufgabe 3: Der Versand

Ein Versandzentrum verschickt Briefe, Pakete und Wertpakete. Setze die Hierarchie so um, dass alle Tests grün werden.

Diese Aufgabe verlangt alles aus diesem Kapitel zugleich: eine Konstante, ein Klassenattribut mit Klassenmethode, eine abstrakte Klasse mit abstrakter Methode, drei Vererbungsebenen und eine Verwaltungsklasse, die keine der Unterklassen kennt.

classDiagram
    class Sendung {
        <<abstract>>
        -MAX_GEWICHT: int$
        -erzeugt: int$
        -empfaenger: String
        -gewichtInGramm: int
        -nummer: int
        +Sendung(pEmpfaenger: String, pGewicht: int)
        +getEmpfaenger() String
        +getGewichtInGramm() int
        +getNummer() int
        +portoInCent() int*
        +versicherungInCent() int
        +endpreisInCent() int
        +getErzeugt() int$
    }
    class Brief {
        +Brief(pEmpfaenger: String, pGewicht: int)
        +portoInCent() int
    }
    class Paket {
        +Paket(pEmpfaenger: String, pGewicht: int)
        +portoInCent() int
    }
    class Wertpaket {
        +Wertpaket(pEmpfaenger: String, pGewicht: int)
        +versicherungInCent() int
    }
    class Versandzentrum {
        -sendungen: Sendung[]
        -anzahl: int
        +Versandzentrum(pPlaetze: int)
        +nimmAn(pSendung: Sendung) boolean
        +gesamtpreisInCent() int
        +schwerste() String
    }
    Sendung <|-- Brief
    Sendung <|-- Paket
    Paket <|-- Wertpaket
    Versandzentrum "1" --> "0..*" Sendung : verschickt

Was das Diagramm nicht sagen kann:

  • MAX_GEWICHT ist 31500. Der Konstruktor deckelt schwerere Sendungen darauf; negative Gewichte werden zu 0.
  • erzeugt zählt alle jemals erzeugten Sendungen. Jede Sendung bekommt beim Erzeugen die nächste Nummer.
  • Brief: bis einschließlich 20 Gramm 85 Cent, darüber 160 Cent.
  • Paket: 500 Cent Grundpreis plus 10 Cent je volles Kilogramm.
  • Wertpaket: genau wie ein Paket, aber mit 250 Cent Versicherung.
  • versicherungInCent() liefert für alle anderen Sendungen 0.
  • endpreisInCent() ist Porto plus Versicherung und steht nur in Sendung.
  • schwerste() liefert den Empfänger der schwersten Sendung, bei leerem Zentrum die leere Zeichenkette.
Tipp 1: die volle Kilozahl

Ganzzahldivision: gewichtInGramm / 1000 liefert bei 2500 den Wert 2 und bei 999 den Wert 0 – genau die vollen Kilogramm.

Tipp 2: Was steht in Wertpaket?

Eine einzige Methode. Das Porto erbt es von Paket, alles andere von Sendung. Wer dort portoInCent() noch einmal hinschreibt, hat dieselbe Rechnung zweimal im Programm.

Tipp 3: endpreisInCent

Eine Zeile, und sie steht nur in Sendung:

return portoInCent() + versicherungInCent();

Beide Aufrufe werden dynamisch gebunden – beim Wertpaket also Paket.portoInCent() und Wertpaket.versicherungInCent(). Zwei Klassen, ein Aufruf.

Tipp 4: schwerste

Wie bestbezahlt in 1.5: Merk dir den Index der bisher schwersten Sendung. Und benutze > und nicht >= – sonst gewinnt bei Gleichstand die spätere.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Aufgabe 4: Abstrakte Klasse oder Schnittstelle? (LK)

Ohne Rechner. Entscheide für jeden Fall und begründe mit einem Kriterium aus der Vergleichstabelle in 1.6.

a) Girokonto und Sparkonto haben beide einen Kontostand, eine Kontonummer und die Methoden einzahlen und abheben. Nur die Zinsberechnung unterscheidet sich.

b) Ein Programm soll Angestellter, Stromrechnung und Mietvertrag in einer gemeinsamen Liste verarbeiten, um daraus die monatlichen Ausgaben zu addieren. Gemeinsam ist ihnen nur, dass jedes einen Betrag liefern kann.

c) Alle Objekte, die sich der Größe nach ordnen lassen sollen, brauchen eine Methode istGroesserAls.

d) In einem Spiel gibt es Zombie, Skelett und Drache – alle mit Lebenspunkten und Position. Der Drache und ein Pfeil sollen zusätzlich fliegen können.

e) Warum ist es in Java ausgeschlossen, dass Angestellter von Mitarbeiter und von Bezahlbar erbt, wenn beides Klassen wären? Und wieso ist das mit einer Schnittstelle kein Problem?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Zum Weiterdenken

Vertiefung: Einen fremden Entwurf beurteilen

Der folgende Entwurf für eine Schulverwaltung ist von jemandem, der das Kapitel nicht gelesen hat. Er übersetzt und läuft.

public class Person {
    public String name;
    public static int alter;
    protected String[] noten;
 
    public String beschreibung() {
        return "eine Person";
    }
}
 
public class Schueler extends Person {
    public String beschreibung() {
        return "ein Schueler";
    }
}
 
public class Schulklasse extends Person {
    private Schueler[] schueler;
 
    public String beschreibung() {
        return "eine Klasse";
    }
}
 
public class Auswertung {
    public String zeigeAn(Person pPerson) {
        if (pPerson instanceof Schueler) {
            return "Schueler: " + pPerson.name;
        }
        if (pPerson instanceof Schulklasse) {
            return "Klasse mit Schuelern";
        }
        return "unbekannt";
    }
}

a) Finde fünf Entwurfsfehler. Benenne zu jedem, welche Regel aus diesem Kapitel verletzt wird.

b) Zwei der Fehler hängen zusammen: Der eine ist die Ursache, der andere die Folge. Welche sind es?

c) Zeichne das Implementationsdiagramm des verbesserten Entwurfs.

d) Beurteile: Welcher der fünf Fehler richtet auf lange Sicht den größten Schaden an? Begründe deine Reihenfolge.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Selbsttest

Rückblick

Teilbare URL erstellen

Abschnitte auswählen