Schnittstellen
Nur Leistungskurs. Diese Lektion gehört zu den zusätzlichen Anforderungen des Leistungskurses. Im Grundkurs kannst du sie überspringen.
Das Problem
Eine abstrakte Klasse löst ein Problem: Sie erzwingt, dass alle Unterklassen eine bestimmte Methode anbieten. Aber sie hat eine harte Grenze.
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.
Warum kannst du dafür keine abstrakte Klasse Beweglich einsetzen?
Auflösung
Weil eine Klasse in Java nur von einer Oberklasse erben kann. Spieler erbt schon von Sprite – für Beweglich ist kein Platz mehr.
Man könnte Beweglich zwischen Sprite und Spieler schieben. Das geht aber nur, wenn wirklich alle beweglichen Dinge auch Sprites sind. Sobald ein bewegliches Objekt kein Sprite ist – etwa eine Kamerafahrt –, bricht der Entwurf.
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 implementieren.
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 implementieren:
implements Bezahlbar, Vergleichbar, Speicherbar. - Eine Schnittstelle ist ein Typ. Deshalb geht
Bezahlbar[] posten– obwohlAngestellterundStromrechnungüberhaupt nichts miteinander zu tun haben.
Im Diagramm schreibt man <<interface>> über den Namen und verbindet die implementierende Klasse mit einem gestrichelten Pfeil mit leerem Dreieck.
classDiagram
class Bezahlbar {
<<interface>>
+double betrag()
+String bezeichnung()
}
class Mitarbeiter {
<<abstract>>
#String name
+double berechneGehalt()*
}
class Angestellter {
-int stufe
+double berechneGehalt()
+double betrag()
+String bezeichnung()
}
class Stromrechnung {
-double monatsbetrag
+double betrag()
+String bezeichnung()
}
Mitarbeiter <|-- Angestellter
Bezahlbar <|.. Angestellter
Bezahlbar <|.. Stromrechnung
Abstrakte Klasse oder Schnittstelle?
| abstrakte Klasse | Schnittstelle | |
|---|---|---|
| Attribute | ja | nein (nur Konstanten) |
| 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.
Entscheide jeweils: abstrakte Klasse oder Schnittstelle? Begründe.
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 sortieren lässt
Auflösung
a) Abstrakte Klasse. Alle Fahrzeuge teilen Zustand (Geschwindigkeit, Position) und Verhalten.
b) Schnittstelle. Ein Bild, ein Spielstand und ein Adressbuch sind grundverschiedene Dinge – gemeinsam ist ihnen nur die Fähigkeit.
c) Abstrakte Klasse. Beide haben einen Kontostand und einen Besitzer.
d) Schnittstelle. Zahlen, Wörter, Personen und Termine haben nichts gemeinsam außer der Vergleichbarkeit.
Aufgabe: Vergleichbar
Diese Schnittstelle brauchst du im Kapitel über Bäume wieder – dort heißt sie in der NRW-Klassenbibliothek ComparableContent.
Setze die Schnittstelle und zwei implementierende 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.
Tipp 1: Der Vergleich in Buch
Der Parameter ist vom Typ Vergleichbar – die Schnittstelle kennt aber 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 hat den Typ Vergleichbar statt int.
Tipp 3: Warum funktioniert das für Bücher und Personen gleichzeitig?
Weil Sortierer gar nicht wissen muss, was es sortiert. Es 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.
Zusatzaufgabe
In der Einführungsphase gab es in der Zusatzaufgabe zur Stadt die Idee, alle Objekte zwischen Tag und Nacht umzuschalten – und in der letzten Lektion die abstrakte Klasse Stadtobjekt als Lösung.
a) Baue es stattdessen mit einer Schnittstelle Umschaltbar. Was ändert sich?
b) Welche Lösung ist hier besser? Begründe mit der Faustregel „ist ein“ gegen „kann etwas“.
c) Was, wenn manche Stadtobjekte umschaltbar sein sollen und andere nicht – etwa ein Zaun, der nachts genauso aussieht? Welche Lösung kommt damit besser zurecht?