Sonntag, 25. Mai 2014

Program to an Interface - freiwillig partielle Interfaces

"Program to an interface, not an implementation." Erstes Prinzip des objektorientierten Entwurfs, [GOF1995].
In den beiden vorhergehenden Blogartikeln (eins, zwei) wurde erläutert, unter welchen Bedingungen die Einführung totaler Interfaces angemessen ist. Nun ist es an der Zeit, sich den partiellen Interfaces zu widmen. Grundsätzlich können zwei Varianten partieller Interfaces unterschieden werden (Details zu dieser Einteilung werden hier erläutert):
  • 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.
Der vorliegende Artikel widmet sich den freiwillig partiellen Interfaces.

Zugriffsbeschränkungen

Das Wesentliche freiwillig partieller Interfaces liegt in der Einschränkung des Zugriffs auf die Aufgerufenen. Um den Zugriff auf Aufgerufene zu kontrollieren, können grundsätzlich sehr unterschiedliche Mechanismen verwendet werden:
  • Zugriffs-Modifizierer (engl. access modifier, im deutschen Sprachraum auch oft Sichtbarkeits-Modifizierer genannt): In zahlreichen objektorientierten Programmiersprachen kann die Sichtbarkeit von Membern mittels der Modifier private, protected und public gesteuert werden. In einigen Sprachen existieren weitere Differenzierungen. So bietet Java bei Abwesenheit eines Zugriffs-Modifizierers die sog. Package-Sichtbarkeit. C# kennt zusätzlich die Sichtbarkeiten protected internal und public internal. All diesen Mechanismen ist gemeinsam, dass sie die globale Sichtbarkeit der Member regeln, ohne Bezug zu speziellen Aufrufern zu nehmen.
  • Während die Zugriffs-Modifizierer auf anonyme Sichtbarkeitsbereiche abstellen, bieten einige Sprachen auch explizite Export-Mechanismen an, welche die Veröffentlichung an konkrete andere Funktionen oder Klassen erlauben. So kennt etwa C++ das friend-Schlüsselwort, das die Veröffentlichung an sogenannte Friend-Funktionen oder Friend-Klassen erlaubt. Eiffel setzt diese Grundidee mittels sogenannter selektiver Exports noch konsequenter um.
  • Das Konzept der inneren oder geschachtelten Typen (innere Klassen, Interfaces und Aufzählungstypen) beruht auf teils subtilen Unterschieden in der Sichtbarkeit von Membern oder der Erzeugbarkeit von Instanzen. Es gibt zahlreiche unterschiedliche Varianten innerer Klassen, die sich je nach Sprache mitunter deutlich unterscheiden.
All diesen Mechanismen zur Zugriffsbeschränkung ist gemeinsam, dass sie bereits zur Deklarationszeit der jeweiligen Typen erfolgen. Die Beschränkung erfolgt gewissermaßen aus der Perspektive des deklarierten Typs, der damit für alle potentiellen Aufrufer die Zugriffsmöglichkeiten festlegt.

Durch freiwillig partielle Interfaces kehrt sich diese Perspektive um: Durch die Verwendung des Interface-Typen beschränkt der jeweilige Aufrufer freiwillig seine Zugriffsmöglichkeiten. Worin liegt die Motivation für diese Beschränkung?

Eine Art "No-Spy-Abkommen"

Durch den Verzicht auf Zugriffsrechte gesteht der Aufrufer dem Aufgerufenen eine größere Privatsphäre zu. Der Aufgerufene wird freier darin, Teile seiner öffentlichen Schnittstelle zu ändern. Diese Strategie kann für den Aufrufer aus verschiedenen Gründen von Vorteil sein:
  • Schutz: Der Aufrufer schützt sich vor einer späteren Ausweitung der Nutzung des Aufgerufenen, die dem ursprünglichen Entwurf zuwiderläuft.
  • Verständlichkeit: Die Verständlichkeit des Quelltextes des Aufrufers wird erhöht, da der Name des verwendeten Interfaces besser ausdrückt, welche Rolle der Aufgerufene eigentlich im Kontext des Aufrufers spielt. 
  • Entkopplung: Schließlich macht sich auch der Aufrufer selbst unabhängiger vom Aufgerufenen, da er von Änderungen seiner öffentlichen Schnittstelle nur noch betroffen sein wird, wenn sie das tatsächlich von ihm verwendete Interface betreffen. Dies ist das Interface Segretation Prinzip: "Software-Elemente sollten nicht von Schnittstellen abhängen, die sie nicht benötigen."
  • Änderbarkeit: Wenn alle Klassen die Privatsphäre anderer Klassen maximal respektieren, resultiert daraus insgesamt ein höheres Maß an Freiheit für Änderungen. Der Einzelne verzichtet zwar konkret auf Zugriffsrechte, darf dafür aber auch erwarten, dass sich andere ihm gegenüber ebenso verhalten. Dies lässt sich als eine Form von "No-Spy-Abkommen" verstehen, das freilich nur dann funktioniert, wenn sich alle Beteiligten daran halten. Die Profiteure dieses Abkommens sind insbesondere diejenigen Elemente des Systems, die häufig in der Rolle des Aufgerufenen agieren. (Da der Aufrufer in anderen Situationen selbst zum Aufgerufenen werden kann, profitiert er indirekt dennoch von seinem Verzicht.)

Nachteile

Es kann auch Situationen geben, in denen der Verzicht auf Zugriffsmöglichkeiten mit spürbaren Nachteilen einhergeht.
  • Wenn es tatsächlich einmal erforderlich sein sollte, weitere Teile der öffentlichen Schnittstelle des Aufgerufenen zu nutzen, muss entweder das Interface geändert oder ein weiteres Interface eingeführt werden. Beides kann mit erheblichen Aufwänden verbunden sein.
  • Insbesondere wenn tatsächlich nur eine einzige Implementierung des Interfaces existiert und wenn diese Implementierung zusätzlich mittels mehrerer Interfaces "partitioniert" wird (freiwillig partielle n:1 Interfaces), kann dies z. T. auch zur Verwirrung beitragen. Die immer gleiche Einheit taucht an verschiedenen Stellen unter verschiedenem Namen auf und kann ggf. nicht unmittelbar wiedererkannt werden. Dies könnte ggf. als Verstoß gegen das Prinzip der Einfachheit gewertet werden. Es ist also wichtig, dass die Partitionierung einer nachvollziehbaren Idee folgt und allen Beteiligten leicht verständlich ist
  • Der Aufwand für die korrekten Instanzerzeugungen kann ggf. steigen. Ist der Aufgerufene beispielsweise ein Singleton, so muss dieses Singleton nun in verschiedenen Typisierungen verfügbar sein und in die Aufrufer injiziert werden.

Alternativen

Oben wurde zusammenfassend erläutert, dass die wesentlichen angestrebten Vorteile bei der Einführung freiwillig partieller Interfaces auf der Einschränkung von Zugriffsmöglichkeiten beruhen. 

Man könnte hier auch von einer rollenbasierten Zugriffsbeschränkung sprechen. Im Einzelfall muss sehr gut geprüft werden, ob die gewünschte Zugriffsbeschränkung tatsächlich mittels partieller Interfaces bestmöglich erreicht werden kann oder ob im jeweiligen System andere Möglichkeiten bestehen, um die gewünschten Vorteile zu erreichen. 

Da es neben den oben genannten programmiersprachlichen Standardmitteln oftmals auch ganz andere Mechanismen für die Zugriffskontrolle gibt (man denke etwa an Publish-Find-Bind-Mechanismen in serviceorientierten Architekturen oder die Import/Export-Möglichkeiten in OSGi), die hier unmöglich alle gewürdigt werden können, kann dies nur ganz allgemein als Hinweis auf mögliche Alternativen erwähnt werden.

Im Einzellfall ist zudem insbesondere zu prüfen, ob einer der genannten Nachteile als so gravierend einzustufen ist, dass die direkte Verwendung der konkreten Klasse die bessere Alternative ist.

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).