Schnittstellen
Nur Leistungskurs. Diese Lektion gehört zu den zusätzlichen Anforderungen des Leistungskurses. Im Grundkurs kannst du sie überspringen – der Rückblick kennzeichnet die Aufgaben, die sie voraussetzen.
Das Problem
Eine abstrakte Klasse löst ein Problem: Sie erzwingt, dass alle Unterklassen eine bestimmte Methode anbieten. Aber sie hat eine harte Grenze.
Aufgabe: Warum reicht eine abstrakte Klasse nicht?
In deinem Spiel gibt es Klassen, die sich bewegen können: Spieler, Gegner, Geschoss. Sie alle erben bereits von Sprite.
Du möchtest zusätzlich erzwingen, dass jede von ihnen eine Methode beschleunige(double pFaktor) anbietet.
a) Warum kannst du dafür keine abstrakte Klasse Beweglich einsetzen?
b) Man könnte Beweglich zwischen Sprite und Spieler schieben. Unter welcher Bedingung geht das – und woran scheitert es, sobald eine Kamerafahrt beweglich sein soll, die kein Sprite ist?
Die Lösung
Eine Schnittstelle (englisch interface) legt fest, welche Methoden eine Klasse anbieten muss – ohne etwas über deren Umsetzung zu sagen und ohne in die Vererbungshierarchie einzugreifen.
Eine Klasse kann von einer Klasse erben, aber beliebig viele Schnittstellen erfüllen.
interface Bezahlbardeklariert nur Signaturen. Alle Methoden sind automatisch öffentlich und abstrakt – man schreibtpublic abstractnicht dazu.class Angestellter extends Mitarbeiter implements Bezahlbar– erst die Oberklasse, dann die Schnittstellen.- Eine Klasse darf mehrere Schnittstellen erfüllen:
implements Bezahlbar, Vergleichbar, Speicherbar. - Eine Schnittstelle ist ein Typ. Deshalb geht
Bezahlbar[] posten– obwohlAngestellterundStromrechnungsonst nichts miteinander zu tun haben und keine gemeinsame Oberklasse besitzen.
Im Diagramm schreibt man <<interface>> über den Namen und verbindet die erfüllende Klasse mit einem gestrichelten Pfeil mit leerem Dreieck.
classDiagram
class Bezahlbar {
<<interface>>
+betragInEuro() int
+bezeichnung() String
}
class Mitarbeiter {
<<abstract>>
#name: String
+berechneGehalt() int*
}
class Angestellter {
-stufe: int
+berechneGehalt() int
+betragInEuro() int
+bezeichnung() String
}
class Stromrechnung {
-monatsbetrag: int
+betragInEuro() int
+bezeichnung() String
}
Mitarbeiter <|-- Angestellter
Bezahlbar <|.. Angestellter
Bezahlbar <|.. Stromrechnung
Abstrakte Klasse oder Schnittstelle?
| abstrakte Klasse | Schnittstelle | |
|---|---|---|
| Attribute mit Zustand | ja | nein |
| Konstanten | ja | ja (public static final) |
| Konstruktor | ja | nein |
| Methodenrümpfe | ja | nein |
| wie viele pro Klasse? | genau eine | beliebig viele |
| Beziehung | „ist ein" | „kann etwas" |
Faustregel: Teilen die Klassen gemeinsamen Zustand und gemeinsames Verhalten? Dann abstrakte Klasse. Teilen sie nur eine Fähigkeit, sind aber sonst grundverschieden? Dann Schnittstelle.
Ein Angestellter ist ein Mitarbeiter. Eine Stromrechnung ist kein Mitarbeiter – aber beide können bezahlt werden.
Eine Konstante in einer Schnittstelle muss in der Online-IDE ausdrücklich public static final heißen und wird in den erfüllenden Klassen mit dem Schnittstellennamen davor angesprochen: Bezahlbar.WAEHRUNG. In echtem Java genügt beides Mal weniger. Konstanten in Schnittstellen sind ohnehin selten – im Zweifel gehören sie in die Klasse, die sie braucht.
Teil 1: Lesen
Aufgabe 1: Sechs Fälle entscheiden
Ohne Rechner. Entscheide für jeden Fall: abstrakte Klasse oder Schnittstelle? Begründe jedes Mal mit einem Kriterium aus der Vergleichstabelle – nicht mit dem Gefühl.
a) Fahrzeug für Auto, Fahrrad, LKW
b) Speicherbar für alles, was sich in eine Datei schreiben lässt
c) Konto für Girokonto und Sparkonto
d) Vergleichbar für alles, was sich der Größe nach ordnen lässt
e) Gegner für Zombie, Skelett, Drache – alle haben Lebenspunkte und eine Position
f) Fliegend für Drache, Pfeil und Kamerafahrt
Aufgabe 2: Was steht so nicht in einer Schnittstelle?
Ohne Rechner. In der folgenden Schnittstelle stecken vier Fehler. Finde sie, sag zu jedem, warum er einer ist, und schreib die berichtigte Fassung auf.
public interface Druckbar {
private int seitenzahl;
public Druckbar(int pSeitenzahl) {
seitenzahl = pSeitenzahl;
}
String kopfzeile();
int seiten() {
return seitenzahl;
}
void drucke();
}
Tipp: eine Schnittstelle sagt nur, was sein muss
Geh die Vergleichstabelle Zeile für Zeile durch. Drei der vier Fehler stehen dort ausdrücklich mit nein in der rechten Spalte.
Der vierte hängt mit dem ersten zusammen: Wenn das eine nicht geht, kann das andere auch nicht gehen.
Teil 2: Schreiben
Aufgabe 3: Vergleichbar
Diese Schnittstelle brauchst du im Kapitel über Bäume wieder – dort heißt sie in der NRW-Klassenbibliothek ComparableContent.
Setze die Schnittstelle und die beiden erfüllenden Klassen so um, dass alle Tests grün werden.
Die Sortiermethode in Sortierer soll beliebige vergleichbare Objekte sortieren können – ohne zu wissen, worum es sich handelt. Sie darf deshalb weder Buch noch Person erwähnen.
Tipp 1: Der Vergleich in Buch
Der Parameter ist vom Typ Vergleichbar – und die Schnittstelle kennt keine Seitenzahl. Du musst ihn also erst in ein Buch umwandeln:
Buch anderes = (Buch) pAnderes;
return seiten > anderes.getSeiten();
Das ist eine der wenigen Stellen, an denen eine Typumwandlung nach unten sachlich richtig ist: Ein Buch kann sich sinnvollerweise nur mit einem anderen Buch vergleichen.
Tipp 2: Der Sortierer
Es ist genau das Sortieren durch Auswählen aus der Einführungsphase. Nur der Vergleich sieht anders aus: statt pWerte[j] < pWerte[kleinstesIndex] heißt es jetzt
pWerte[kleinstesIndex].istGroesserAls(pWerte[j])
Und der Merker beim Tauschen hat den Typ Vergleichbar statt int.
Tipp 3: Warum funktioniert das für Bücher und Personen zugleich?
Weil Sortierer gar nicht wissen muss, was er sortiert. Er weiß nur: Jedes Element kann sich mit einem anderen vergleichen. Wie es das tut, ist Sache der jeweiligen Klasse.
Genau dafür sind Schnittstellen da.
Zum Weiterdenken
Vertiefung 1: Zwei Fähigkeiten auf einmal
Eine Klasse darf beliebig viele Schnittstellen erfüllen – und wird dadurch in mehreren, voneinander unabhängigen Zusammenhängen verwendbar.
a) Sag voraus, was das Programm ausgibt.
b) Notiz steht in zwei Feldern verschiedenen Typs. Erkläre, warum dasselbe Objekt beide Male hineinpasst, und was in jedem Zusammenhang von ihm sichtbar ist.
c) Ergänze eine Klasse Termin, die nur Speicherbar erfüllt. Was ändert sich am Hauptprogramm? Was ändert sich an den beiden Feldern?
d) Beurteile: Warum ist es besser, zwei kleine Schnittstellen mit je einer Methode zu haben, als eine große mit beiden?
Vertiefung 2: Warum gibt es keine Mehrfachvererbung?
Java erlaubt beliebig viele Schnittstellen, aber nur eine Oberklasse. C++ erlaubt beides. Der Grund für Javas Entscheidung heißt Diamantproblem.
a) Angenommen, Angestellter erbte von Mitarbeiter und von Bezahlbar, und beide wären Klassen mit einer Methode zahle() mit Rumpf. Welche Frage kann dann niemand beantworten?
b) Bei Schnittstellen stellt sich diese Frage nicht. Warum nicht? Was fehlt ihnen, das den Konflikt erst entstehen lässt?
c) Zwei Schnittstellen deklarieren beide int wert(); – mit identischer Signatur. Eine Klasse erfüllt beide. Gibt es hier ein Problem?
d) Seit Java 8 dürfen Schnittstellen Standardmethoden mit Rumpf haben (default int wert() { return 0; }). Damit ist ein Teil des Diamantproblems zurück. Wie könnte Java das gelöst haben? Überlege selbst, bevor du nachschlägst.