"Program to an interface, not an implementation." Erstes Prinzip des objektorientierten Entwurfs, [GOF1995].In den vorausgehenden Blogartikeln wurden ausführlich die Vorzüge und potentiellen Gegenanzeigen von Interfaces behandelt. Im vorliegenden Artikel soll nun eine Frage diskutiert werden, die bei jeder Einführung eines abstrakten Typs gestellt werden sollte. (Aus irgendeinem Grund hat sich diese Frage auch als eine häufige Frage in Bewerbungsgesprächen etabliert).
Interface oder abstrakte Klasse?
Die Frage wird von Erich Gamma selbst in einem Interview aufgeworfen:"Eine Frage ist, ob man immer Java-Interfaces verwenden sollte. Eine abstrakte Klasse ist ebenfalls eine gute Möglichkeit. Tatsächlich ermöglichen abstrakte Klassen mehr Flexibilität hinsichtlich der weiteren Evolution. Sie ermöglichen es, neues Verhalten hinzuzufügen, ohne die Klienten zu brechen." [Gamma2004]Mit den in Java 8 eingeführten Default-Methoden in Interfaces ist diese Argumentation allerdings bereits wieder überholt - ein weiteres Beispiel dafür, dass die Diskussion von Konzepten immer wieder von den Realisierungsdetails der konkreten Sprache oder Entwicklungsumgebung beeinflusst wird.
In Effective Java, 2nd Edition lautet hingegen Item 18 ganz pauschal:
"Prefer interfaces to abstract classes" [Bloch2008]Als zentrale Argumente werden die folgenden vorgetragen:
- Durch Verwendung eines Interfaces vermeidet man die z. T. erheblichen Probleme, die sich aus der Einbindung der Subklassen in eine Vererbungshierarchie ergeben würden. Bloch argumentiert hier auf ähnliche Weise wie bei der Erläuterung des Composit Reuse Prinzips (aka Favour composition over inheritence). Die Verwendung von Interfaces erlaubt insbesondere nicht-hierarchische Typisierungen (z. B. Implementierung mehrerer Interfaces durch eine Klasse oder die Implementierung eines Interfaces durch diverse Klassen, die unzusammenhängend über die Vererbungshierarchie verteilt sind) und muss daher nicht die subtilen Kopplungen in Kauf nehmen, die sich innerhalb einer Vererbungshierarchie ergeben können.
- Bloch hebt auch die Möglichkeit der Kombination von Interfaces und abstrakten Klassen hervor, die sogenannten Skelettimplementierungen (engl. skeletal implementations). Dabei wird der eigentliche Typ durch ein Interface deklariert, welches auch von allen Nutzern zur Typdeklaration verwendet wird. Zusätzlich wird durch eine abstrakte Klasse, die das Interface teilweise implementiert, einige Standardfunktionalität bereitgestellt, welche die Implementierung des Interfaces ggf. vereinfacht oder wiederverwendbare Standardmethoden bereitstellt. Dem Nutzer bleibt dann die Freiheit, sich in die Vererbungshierarchie der Skelettimplementierung einzubinden oder direkt vom Interface abzuleiten:
Auch Microsoft und Oracle haben sich zu dem Thema geäußert: Microsoft im MSDN-Artikel Recommendations for Abstract Classes vs. Interfaces, Oracle im Tutorial-Beitrag Abstract Classes compared to Interfaces. Die dort vorgetragenen Argumente lassen sich wie folgt zusammenfassen.
Die Verwendung einer abstrakten Klasse ist naheliegend, wenn
- eine oder mehrere Methoden von allen Subklassen geteilt werden sollen (Microsoft, Oracle)
- sich die Subklassen mehrere Felder teilen oder wenn nicht-statische Felder deklariert werden sollen (Oracle)
- einzelne geteilte Methoden nicht public sein sollen (Oracle)
- lediglich verschiedene Varianten oder Versionen einer Komponente erstellt werden sollen (Microsoft)
- die verschiedenen Implementierungen eng verwandt sind (Microsoft)
- die zu entwickelnde Funktionseinheit tendentiell groß ist (Microsoft)
- die Möglichkeit zur Mehrfachvererbung genutzt werden soll (Oracle)
- die zu erstellende Funktionalität von einer großen Zahl sehr unterschiedlicher, unzusammenhängender Implementierungen geteilt werden soll (Microsoft, Oracle)
- die zu entwickelnde Funktionseinheit tendentiell klein ist (Microsoft)
- das Verhalten eines speziellen Datentyps spezifiziert werden soll, ohne dass die konkrete Implementierung eine Rolle spielt (Oracle)
Zusammenfassung
Es sind in der Regel sehr konkrete Argumente, welche die Verwendung einer abstrakten Klasse erforderlich machen, beispielsweise das erforderliche Teilen gemeinsamer Felder oder Methoden durch alle Implementierungen oder der Wunsch, einzelne Methoden nicht public zu deklarieren. Häufig lassen sich diese Situationen allerdings durch die Nutzung von Skelettimplementierungen lösen. Dennoch kann es trotz aller grundsätzlicher Bedenken auch einmal angebracht sein, eine echte Vererbungshierarchie aufzubauen, an deren Spitze eine abstrakte Basisklasse steht. Dies wird insbesondere dann der Fall sein, wenn alle Implementierungen eng verwandt sind und sich nicht über verschiedene Komponenten verteilen.Das Hauptargument gegen die Verwendung von Interfaces bestand in der Vergangenheit in der erschwerten Evolution einmal veröffentlichter Interfaces. Mit den Default-Methoden in Java 8 könnte diesem Argument zumindest teilweise die Luft ausgehen - man wird beobachten müssen, wie sich Default-Methoden in der Praxis schlagen. In Sprachen, die keinen Mechanismus zur Implementierung von Default-Methoden bereitstellen, muss allerdings das Problem der Schnittstellen-Evolution im Auge behalten werden. In den meisten anderen Fällen ist die Verwendung von Interfaces oder einer Kombination von Interface mit Skelettimplementierung angebracht. Diese vermeiden die problematischen Eigenschaften einer Vererbungshierarchie und bieten daher eine maximale Entkopplung des verwendeten Typs von seiner Implementierung.
Siehe auch
Alle Artikel der Serie "Program to an Interface ...":Quellen
[Bloch2008] - Effective Java 2nd Edition, Joshua Bloch (2008)[Gamma2004] - Design Principles from Design Patterns - A conversation with Erich Gamma, Interview von Bill Venners, siehe http://www.artima.com/lejava/articles/designprinciples.html
[GOF1995] - Design Patterns - Elements of Reusable Software, E. Gamma, R. Helm, R. Johnson, J. Vlissides, (Addison‐Wesley, 1995)