Freitag, 23. Mai 2014

Program to an Interface - totale 1:1-Interfaces

"Program to an interface, not an implementation." Erstes Prinzip des objektorientierten Entwurfs, [GOF1995].
Im vorletzten Blogartikel haben wir Interfaces in vier Kategorien eingeteilt. Von diesen behandeln wir im vorliegenden Artikel totale 1:1-Interfaces:
  • totale 1:1-Interfaces
  • totale 1:n-Interfaces
  • notwendig partielle Interfaces
  • freiwillig partielle Interfaces

Das Reused Abstractions Prinzip als Gegenargument

Alle Argumente, die zuvor für den Einsatz totaler 1:n-Interfaces angeführt wurden, insbesondere
  • redundanzfreie Fallunterscheidungen zur Laufzeit des Systems (Single Choice Prinzip)
  • Offenheit für Erweiterungen über die Lebenszeit des Systems (Open-Closed Prinzips)
  • Austausch von Implementierungen zur Konfigurationszeit des Systems (z. B. per Dependency Injection)
sind für totale 1:1-Interfaces irrelevant. Totale 1:1-Interfaces stehen daher besonders im Verdacht, überflüssig zu sein. Dies drückt sich beispielsweise in einer Art Gegenprinzip aus, das der Blogger Jason Gorman unter der Bezeichnung Reused Abstractions Principle (RAP) wie folgt formuliert:
"Die Entdeckung von Abstraktionen impliziert, dass die Abstraktionen mehr als ein Mal verwendet werden. Superklassen haben mehr als eine Subklasse. Interfaces werden mehr als ein Mal implementiert. Abstrakte Methoden werden mehr als ein Mal überschrieben. Und so weiter." [Gorman2010]

Argumente für totale 1:1-Interfaces

Erinnern wir uns an die grundlegende Motivation für die Einführung totaler Interfaces. Diese 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]
Der hier gemeinte Austausch kann sich bei totalen 1:1-Interfaces offensichtlich nur auf die Lebenszeit des Systems beziehen. Unter welchen Umständen wäre es aber nachteilig, dann einfach die Implementierung selbst auszutauschen? Folgende Szenarien sind denkbar:
  • Interface und Implementierung könnten von zwei verschiedenen Teams entwickelt werden. In diesem Fall stellt das Interface eine Vorgabe dar, welche von der Implementierung erfüllt wird. Die Nutzung des Interfaces dient hier der arbeitsteiligen Software-Konstruktion.
  • Das Interface könnte aus einem modellgetriebenen Entwicklungsprozess heraus entstanden sein, der beispielsweise die Definition öffentlicher Schnittstellen in UML verlangt, um daraus die entsprechenden Interfaces zu generieren. Auf diese Weise kann z. B. sichergestellt werden, dass jederzeit ein aktuelles UML-Modell der zentralen Interfaces der Anwendung existiert, das den Architekten einen Überblick des Gesamtsystems gewährleistet.
  • Das Interface könnte in einer anderen Deployment-Einheit enthalten sein als die Implementierung. Dies könnte z. B. dann der Fall sein, wenn es Gründe gibt, das Deployment der Implementierung vom Deployment des Interfaces zu entkoppeln. Eine solche Entkopplung kann im Rahmen des Abhängigkeits-Managements der Deployment-Einheiten wünschenswert sein, um Abhängigkeiten zu reduzieren oder separat zu versionieren.
  • Die Implementierung könnte eine hohe Austauschwahrscheinlichkeit aufweisen. Dies ist beispielsweise bei Prototypen der Fall oder dann, wenn künftige gravierende Änderungen bereits bekannt sind. 
  • Der Austausch einer Implementierung gegen eine andere könnte aus irgendeinem anderen Grund zu hohen Folgeaufwänden führen, etwa dann, wenn die neue Implementierung die alte nicht einfach ersetzen kann und die daraus resultierende Umbenennung mittels der verfügbaren Refactoring-Werkzeuge nicht problemlos durchgeführt werden kann. In diesem Fall könnte die Einführung eines Interfaces auch dann sinnvoll sein, wenn die Austauschwahrscheinlichkeit eher gering ist, da der zusätzliche Aufwand für die Einführung solcher Interfaces gegenüber den potentiell hohen Kosten zu vernachlässigen ist.
Ist keiner der beschriebenen Punkte erfüllt, so kann der Verzicht auf ein Interface vernünftig sein.

Eine Kosten-Ungleichung

Die Abwägung, die in den beiden letztgenannten Punkten vorgenommen wird, läuft auf folgende Kosten-Ungleichung hinaus:

Kosten für das Interface < Erwartete Austauschkosten

Trifft diese Bedingung zu, so ist die Einführung des Interfaces als lohnenswert zu betrachten. Dabei  berechnen sich die Erwarteten Austauschkosten als das Produkt der Austauschwahrscheinlichkeit für die Implementierung mit den tatsächlichen Austauschkosten im Falle eines Austauschs der Implementierung, also:

Erwartete Austauschkosten = Austauschwahrscheinlichkeit * Austauschkosten

Zusammenfassung

Die Einführung totaler 1:1-Interfaces muss wohlbegründet sein. Als Gründe kommen arbeitsteilige Aspekte, modellgetriebene Entwicklungstechnologien, Entkopplung des Deployments, eine hohe Austauschwahrscheinlichkeit oder unvermeidbar hohe Kosten im Falle eines Austauschs in Betracht. Als Abwägungshilfe kann im Einzelfall die oben genannte Kosten-Ungleichung dienen.

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).
[Gorman2010] - Jason Gormans Blogartikel "Reused Abstractions Principle (RAP)" - http://codemanship.co.uk/parlezuml/blog/?postid=934
[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