Donnerstag, 15. Mai 2014

Program to an Interface - Kategorisierung von Interfaces

"Program to an interface, not an implementation." Erstes Prinzip des objektorientierten Entwurfs, [GOF1995].
Interfaces können auf unterschiedliche Weisen eingeteilt werden. In [Steimann2005] wird eine Kategorisierung unternommen, die mir bei der weiteren Betrachtung sehr hilfreich war. Darin werden Interfaces nach ihrer jeweiligen Bedeutung für den Aufrufer und den Aufgerufenen unterschieden:
  • Der Aufrufer verfügt über eine Objektinstanz, die von einer Variablen des Interface-Typs referenziert wird.
  • Der Aufgerufene ist genau diese Objektinstanz. Das Typstem stellt sicher, dass die Implementierung dieser Objektinstanz ein Subtyp des Interface-Typen ist.
Die Einteilung in [Steimann2005] erweist sich allerdings in mancher Hinsicht als unhandlich, da sie einbezieht, wer eigentlich durch das Interface eine Dienstleistung in Anspruch nimmt: der Aufrufer, der Aufgerufene, beide zugleich oder gar ein Dritter. Da dies jedoch nicht immer klar ist, wird hierdurch einige Unschärfe in die Kategorienbildung eingebracht, die es erschwert hätte, klare Kriterien für oder gegen die Verwendung von Interfaces abzuleiten. Ich habe mich daher für eine etwas einfachere Einteilung entschieden, die lediglich die strukturellen Eigenschaften der Interfaces betrachtet.

Totale Interfaces

Eine wichtige Gruppe von Interfaces stellen die totalen Interfaces dar. Ein totales Interface stellt alle öffentlichen Eigenschaften des Aufgerufenen zur Verfügung. Der einzige Grund für die Existenz totaler Interfaces besteht darin, "die Spezifikation des Aufgerufenen von seiner Implementierung zu trennen, so dass letztere ausgetauscht werden kann, ohne dass die Aufrufer davon Notiz nehmen müssen" [Steimann2005]. Totale Interfaces kommen in zwei Varianten vor:
  • Zu einem gegebenen Zeitpunkt existiert nur genau eine Implementierung des Interfaces innerhalb eines Systems. Diese Implementierung kann aber über die Lebenszeit des Systems ausgetauscht werden.
  • Zu einem gegebenen Zeitpunkt existieren (zumindest potentiell) mehrere Implementierungen des Interfaces innerhalb eines Systems. Welche davon im Aufrufer verwendet wird, kann zur Laufzeit ausgewählt oder ausgetauscht werden.
Ich möchte bei der ersten Variante von totalen 1:1-Interfaces, bei der letzteren von totalen 1:n-Interfaces sprechen, da ich die Terminologie aus [Steimann2005] hier für zu metaphorisch halte. (Siehe Anmerkung 1.)

Partielle Interfaces 

Den totalen Interfaces stehen partielle Interfaces gegenüber, die nur einen Teil der öffentlichen Eigenschaften des Aufgerufenen spezifizieren. Zum Zeitpunkt der Deklaration eines partiellen Interfaces kann ein Aufgerufener bereits bekannt sein oder auch nicht. Welche Methoden in das Interface aufgenommen werden, wird im Gegensatz zu totalen Interfaces weniger von den Aufgerufenen als von den Aufrufenden bestimmt. Hier können zwei Situationen unterschieden werden:
  • Notwendig partielle Interfaces: Zur Deklarationszeit des Interfaces sind die Aufgerufenen noch gar nicht (alle) bekannt, so dass auch nur partiell definiert werden kann, welche Eigenschaften von ihnen in das Interface aufgenommen werden.
  • Freiwillig partielle Interfaces: Zur Deklarationszeit des Interfaces sind zwar bereits alle Aufgerufenen bekannt, das Wissen über deren öffentliche Eigenschaften soll aber in den Aufrufern bewusst minimal bleiben.

Gleichzeitig total und partiell

Da sich die Eigenschaft "total oder partiell" immer nur in Bezug auf eine Klasse beurteilen lässt, ist es natürlich strukturell möglich, dass ein Interface im obigen Sinne gleichzeitig total und partiell ist. Insgesamt ist ein solches Interface allerdings in seinem Wesen partiell, da es nicht mehr alle öffentlichen Eigenschaften seiner Aufgerufenen abdeckt und somit auch in den Aufrufern, die ggf. alle diese Eigenschaften benötigen, nicht mehr verwendet werden kann. Es kann allerdings auch die Frage gestellt werden, ob eine solche Konstellation auf ein mögliches Design-Problem hindeutet, das wie folgt aussehen könnte:
  • Ein Interface wurde zwar als totales Interface entworfen, dieser Entwurf wurde aber inzwischen von einzelnen Implementierungen unterwandert, ohne dass das Interface entsprechend aktualisiert wurde. Womöglich haben die Implementierungen neue Eigenschaften auch unnötigerweise öffentlich implementiert.
  • Ein Interface wurde zwar als partielles Interface entworfen, wurde aber nach und nach derart erweitert, dass es für einzelne Aufgerufene total geworden ist. Dann wäre es ggf. angeraten, die Abstraktion auf alle Aufgerufenen auszudehnen und das Interface zu einem totalen 1:n-Interface zu machen.

Zusammenfassung

Die oben beschriebene Einteilung von Interfaces in vier Kategorien lässt sich wie folgt als Entscheidungsbaum zusammenfassen:


In den folgenden Blogartikeln kann nun für jede der gefundenen Kategorien detailliert untersucht werden, welche Kriterien die Einführung eines Interfaces der jeweiligen Kategorie (im Gegensatz zur direkten Verwendung einer Implementierung) ratsam erscheinen lässt.

Siehe auch

Alle Artikel der Serie "Program to an Interface ...":

Einführung
Kategorisierung von Interfaces
Totale 1:n-Interfaces
Totale 1:1-Interfaces
Freiwillig partielle Interfaces
Notwendig partielle Interfaces
Fazit und Gegenbeispiele
Interface oder abstrakte Klasse?
Werkzeugunterstützung
Das Prinzip

Quellen

[GOF1995] - Design Patterns - Elements of Reusable Software, E. Gamma, R. Helm, R. Johnson, J. Vlissides, (Addison‐Wesley, 1995)
[Steimann2005] - Patterns of interface‐based programming, F. Steimann, P. Mayer, Journal of Object Technology 4:5, 75–94 (2005), http://www.jot.fm/issues/issue_2005_07/article1.pdf

Anmerkungen

1)  [Steimann2005] spricht von "allgemeinen" Interfaces an Stelle von totalen, um damit zu signalisieren, dass für diese Interfaces keine konkrete Nutzerschaft vorgesehen ist. Da allgemeine Interfaces aber für ihn immer auch total im obigen Sinne sind, kann auch die weitere Einteilung übernommen werden. Totale 1:1-Interfaces werden dort als "idiosynkratische Interfaces" bezeichnet,  totale 1:n-Interfaces als "Familieninterfaces".