Donnerstag, 24. April 2014

Abstraktheit - abstrakte Klassen und Interfaces

Abstrakte Klassen und Interfaces sind die zentralen Entitäten, die in Martins Sinn als "Abstraktionen" zu bezeichnen sind. In verschiedenen Programmiersprachen treten abstrakte Klassen und Interfaces in verschiedenen Erscheinungsformen auf:
  • C++ kennt auf Typ-Ebene kein entsprechendes Schlüsselwort. Eine Klasse wird implizit abstrakt, indem eine ihrer Methoden mit dem Schlüsselwort virtual deklariert wird.
  • Java kennt auf Klassen-Ebene den Modifier abstract und stellt für die Deklaration von Interfaces explizit das Schlüsselwort interface bereit. Dies gilt auch für C# und viele andere Sprachen.
  • Eiffel betont den Aspekt der verzögerten Implementierung und stellt den Modifier deferred zur Verfügung. Dementsprechend bezeichnet Bertrand Meyer abstrakte Klassen in [Mayer1998] als deferred classes.
  • Scala kennt den Modifier abstract für Klassen. An Stelle von Interfaces wird jedoch das Trait-Konzept (über das Schlüsselwort trait) zur Verfügung gestellt. Dieses Konzept wird inzwischen in einer wachsenden Zahl von Programmiersprachen genutzt, die z. T. zusätzlich auch noch das interface-Schlüsselwort bereitstellen.
Dieser kurze Überblick soll hier genügen. In der Folge werde ich der Einfachheit halber von "Abstraktionen" reden und damit alle Erscheinungsformen meinen, die im weitesten Sinne als die abstrakten Gegenstücke zu konkreten Klassen interpretiert werden können.

Was macht Abstraktionen aus?

Abstraktionen weisen folgende elementare Eigenschaften auf, die sie von konkreten Klassen unterscheiden:
  • Unvollständige Implementierung: Abstraktionen ermöglichen die unvollständige und verzögerte Implementierung konkreter Klassen und Methoden.
  • Erforderliche Implementierung: Abstraktionen erfordern die Implementierung konkreter Klassen und Methoden, wenn sie in einem konkreten System verwendet werden sollen.
  • Verhinderung der Erzeugung von Instanzen: Es können keine Instanzen von Abstraktionen erzeugt werden.
  • Entkopplung durch Dependency Inversion: Klienten verlieren ihre Abhängigkeit von konkreten Klassen.
Diese Eigenschaften werden in den folgenden Abschnitten etwas ausführlicher behandelt.

Unvollständige Implementierung

Die Möglichkeit zur unvollständigen Implementierung schafft einerseits Vorteile im Entwicklungsprozess: Es können bereits Elemente auf einer früheren und höheren Entwurfsebene entwickelt werden, ohne sich um alle Details der späteren Realisierung kümmern zu müssen. [Mayer1998] betont diesen Aspekt und spricht daher auch von deferred classes: Die Implementierung ist auf später verschoben.

Andererseits ermöglicht unvollständige Implementierung aber auch strukturell andere Ansätze: Es können beispielsweise Bibliotheken, Frameworks oder ganze Software-Systeme als releasefähige, wiederverwendbare Einheiten zur Verfügung gestellt werden, ohne das konkrete Einsatz-Szenario bereits zu kennen.

Es ist anzumerken, dass dies auch ohne Abstraktionen möglich wäre, da freilich auch konkrete Klassen erweiterbar sind. Dies ginge allerdings mit unschönen Standardimplementierungen einher, die den Klienten zudem nicht zur Implementierung der in der Abstraktion unvollständigen Teile zwingen.

Erforderliche Implementierung

Im Gegensatz zur eben erwähnten Bereitstellung einer konkreten Standardimplementierung zwingt eine Abstraktion den Entwickler dazu, selbst eine konkrete Implementierung zu erstellen. In einigen Fällen kann dies das primäre Entwurfsziel sein: Den Entwickler daran zu erinnern, dass es zur Verwendung der (abstrakt) bereitgestellten Funktionalität noch erforderlich ist, bestimmte konkrete Aspekte zu berücksichtigen. Abstraktion in diesem Sinne verhindert eine gedankenlose oder lückenhafte Wiederverwendung.

Verhinderung der Erzeugung von Instanzen

Ein Klient, der eine Abstraktion verwendet, hat noch keine Entscheidung getroffen, von welchem Typ seine späteren konkreten Instanzen sind. Dies bewirkt zweierlei:
  • Der Klient darf sich nicht auf irgendein zufälliges konkretes Verhalten verlassen, sondern ausschließlich auf die öffentliche Schnittstelle der Abstraktion. 
  • Es muss (in der Regel außerhalb des Klienten) eine bewusste Entscheidung getroffen werden, welchen konkreten Typ die Instanz erhält. Diese Entscheidung könnte z. B. in einer Factory oder in einer Dependency Injection-Konfiguration getroffen werden. Die Verantwortlichkeit für diese Entscheidung wird damit an eine passendere Stelle verlagert.

Entkopplung durch Dependency Inversion

Aus der Tatsache, dass ein Klient, der Abstraktionen verwendet, den Typ der Instanzen in der Regel nicht kennt (dies tut er nur dann, wenn er die Instanz selbst erzeugt, und das sollte er nicht tun), folgt auch, dass er von der konkreten Klasse entkoppelt ist. Diese Entkopplung tritt in Form einer Dependency Inversion auf: Der Klient ist nicht mehr von der konkreten Klasse abhängig, sondern die konkrete Klasse ist von der Abstraktion abhängig. Die folgende Abbildung veranschaulicht dies:
Grundsätzlich wäre auch diese Entkopplung (ebenso wie die verzögerte Implementierung) mit einer konkreten Standardimplementierung denkbar, freilich mit denselben unschönen Nebeneffekten, die oben bereits erwähnt wurden.

Zusammenfassung

Abstraktionen ermöglichen die Bereitstellung unvollständiger Implementierungen als releasefähige Einheiten bis hin zu Software-Systemen mit großem Funktionsumfang. Sie zwingen oftmals den Entwickler, der sie verwenden will, eine eigene Implementierung zu erstellen und so die Funktionen zu ergänzen, die bewusst unvollständig geblieben sind. Sind solche Implementierungen bereits verfügbar, so muss zumindest noch eine bewusste Entscheidung getroffen werden, welche verwendet werden soll. Diese Entscheidung wird meist nicht in den einzelnen Klienten getroffen, sondern von einer anderen, dafür verantwortlichen Einheit. Auf diese Weise wird eine Entkopplung erreicht, die den vergleichsweise einfachen Austausch konkreter Implementierungen erlaubt. All dies ist durch Abstraktionen möglich, ohne die Nachteile konkreter Standardimplementierungen in Kauf nehmen zu müssen. 

Im nächsten Blogartikel wird mit dem nun konkretisierten Wissen über den Nutzen von Abstraktionen untersucht, ob Abstraktheit in Martins Sinn, also höhere oder weniger hohe Abstraktions-Anteile in konkreten Mengen von Klassen, irgendeinen Bedeutungsgehalt aufweist.

Quellen

[Mayer1998] - Objekt-oriented Software Construction 2nd Edition, Bertrand Meyer (1998)