- Fluent Interface
-
Fluent Interfaces[1] stellen in der Software-Entwicklung eine Art Programmierschnittstelle dar, die es ermöglicht, beinahe in Satzform Funktionalitäten aufzurufen. Der daraus resultierende Programmcode ist leicht lesbar und macht somit das Verständnis über dessen Intention leicht. Fluent Interfaces können Grammatiken realisieren, die dafür sorgen, dass der Benutzer einer solchen Schnittstelle immer einen gültigen Satz schreibt, also die Schnittstelle richtig verwendet. Es gibt zwei Arten, solche flüssigen Schnittstellen zu realisieren, Method Chaining[2] (Methodenketten) und Nested Functions[3] (Eingebettete Funktionen).
Inhaltsverzeichnis
Grundlagen
Als Begründer von Fluent Interfaces gelten Eric Evans und Martin Fowler. Mit der Beispielimplementierung des Entwurfsmusters Specification[4] brachten diese den Ansatz hervor, neue Objekte mit Hilfe von Methodenketten auf flüssige Weise zu erstellen.
Specification colorSpec = new ColorSpecification(); Specification lengthSpec = new LengthSpecification(); if(colorSpec.and(lengthSpec).isSatisfiedBy(obj)) { ... }
Wie man im oberen Beispiel sieht, wird ganz explizit geschrieben, dass ein Objekt auf beide Bedingungen getestet wird. Ein weiteres Beispiel wäre das flüssige Erstellen eines Datums.
DateFactory.newDate().year(2009).month(2).day(07);
Im Vergleich zur Verwendung eines Konstruktors sieht man explizit, welche Rolle die einzelnen Werte spielen. Hinzu kommt, dass der Entwickler einer solchen Schnittstelle die Reihenfolge, in der die Methoden aufgerufen werden dürfen, reglementieren kann. Somit können z.B. Methodenaufrufe, die mehrere Parameter erwarten, wesentlich verständlicher, expliziter, gemacht werden.
Gerade in Evans’ Domain-Driven Design spielen Fluent Interfaces eine besondere Rolle, denn sie dienen dazu, spezifische Eigenschaften aus einer Domäne explizit im Programmcode auszudrücken. Fluent Interfaces gehören damit zu den sogenannten Internen Domänenspezifischen Sprachen[5] (in[6] auch als Eingebettete Sprache bezeichnet). Dabei handelt es sich um Domänenspezifische Sprachen, die direkt mit den Mitteln einer Programmiersprache realisiert sind.
Implementierung
Naiv ohne Grammatik
Die Beispielimplementierung von Evans und Fowler für Specifications folgte einem naiven Ansatz. Um die oben gezeigte Methodenkette zu realisieren, genügte es dem Interface Specification einfach, die nötige Methode and() hinzuzufügen.
public interface Specification { Specification and(Specification spec); boolean isSatisfiedBy(Object obj); }
Mit dem Aufruf der verknüpfenden Methode and() wird also immer eine Specification geliefert, an der man wiederum die Methode and() aufrufen könnte. Jedoch wird durch diesen naiven Ansatz die Implementierung von Typen um Funktionalitäten angereichert, die im Grunde nichts mit dem eigentlich Sinn dessen zu tun haben. Der eigentliche Nachteil ist aber, dass man nicht kontrollieren kann, in welcher Reihenfolge die Methoden aufgerufen werden dürfen.
Mit Grammatik
Häufig spielt die Reihenfolge, in der die Methoden einer Methodenkette aneinander gereiht werden dürfen, eine große Rolle. Das folgende Beispiel zeigt die Verwendung eines Fluent Interfaces, das einem Objekt vom Typ Date einige Tage und Stunden hinzufügt.
Date date = CalendarUtils .add(5).days() .add(10).hours() .to(date);
Würde man wie im naiven Ansatz mit jedem Aufruf einer Methode immer den gleichen Typen zurückliefern, wäre es möglich, den Satz eventuell vorzeitig oder falsch zu beenden. Um die gewünschte Grammatik zu realisieren, muss also mit dem Aufruf einer Methode ein anderer Typ zurückgegeben werden, der die richtigen Folge-Methoden bereithält. Folgendes Beispiel zeigt, dass der Aufruf der Methode newDate() von DateUtils zur Rückgabe eines Mediators führt. Dieser hält dann die Folge-Methode add bereit. Der Aufruf der Methode add wiederum führt ebenfalls zur Rückgabe eines neuen Mediator usw.
public class DateUtils { public static Mediator newDate() { ... } } public class Mediator { public Mediator2 add(int i) { ... } } public class Mediator2 { public Mediator3 days() { ... } } ... // possible sentence DateUtils.newDate().add(5).days(). ...
Bernd Schiffer bezeichnet diese Mediatoren auch als Deskriptoren.[7] Mit obigem Beispiel wird also eine Grammatik realisiert, die genau vorgibt, in welcher Reihenfolge die Methoden aufgerufen werden dürfen. Des Weiteren liefert die Methodenkette solange kein Objekt vom Typ Date, bis diese richtig beendet wird. Somit wird man schon durch Kompilierungsfehler und vor Laufzeit einer Anwendung auf eventuelle Fehler aufmerksam gemacht.
Vorteile
Die Verwendung von Fluent Interfaces bietet einige Vorteile, die sowohl die Programmentwicklung erleichtern als auch die Verständlichkeit des Programmcodes fördern.
- Fluent Interfaces können einem natürlich-sprachlichen Satz ähneln. Das führt dazu, dass zusätzliche Kommentare im Programmcode überflüssig werden.
- Durch ein satzähnliches Fluent Interface ist es dem Benutzer möglich, klare Erwartungen an den Satzaufbau, also die bereitgestellten Funktionalitäten, zu stellen.
- Die Autovervollständigung einer Entwicklungsumgebung wie Eclipse hilft dabei, herauszufinden, welche Methoden als Nächstes aufgerufen werden dürfen.
- Durch Kompilierungsfehler wird man schon vor der Laufzeit auf die falsche Verwendung eines Fluent Interfaces hingewiesen.
Nachteile
Die Realisierung einer Grammatik für Fluent Interface ist sehr umständlich. Das notwendige Netzwerk von Mediatoren wird schnell unübersichtlich. Zudem lässt sich auf dieser Ebene schwer nachvollziehen, welche Satzkonstruktionen möglich sind. Unter[8] ist ein Ansatz zu finden, bei dem versucht wird, dieses Defizit aufzuheben, indem man die Grammatik von Fluent Interfaces in Form von Diagrammen modelliert. Aus einem solchen Modell wird dann der notwendige Code automatisch generiert, sodass es nur noch nötig ist, das Verhalten des Fluent Interfaces zu implementieren.
Die langen Ketten von Methodenaufrufen erschweren das Debuggen, da ein Callstack typischerweise nur die Zeile des Fehlers enthält, nicht aber die Spalte im Source-File. Das gleiche gilt für die Zuordnung von Warnungen aus der statischen Codeanalyse.
Einsatzmöglichkeiten
Fluent Interfaces können für verschiedene Zwecke eingesetzt werden. Im Vordergrund steht dabei immer, explizit zu machen, was in einer Domäne verankert ist.
- Wrapping von Funktionalitäten
- Wie oben bereits dargestellt, können Fluent Interfaces dazu verwendet werden, bestehende Funktionalitäten verständlicher bereitzustellen.
- Flüssiger Erbauer
- In[7] wird das Konzept des flüssigen Erbauers gezeigt. Dabei wird das Konzept des Fluent Interfaces auf das Entwurfsmuster Erbauer übertragen.
- Abbildung fremder Syntax
- Mit Hilfe von Fluent Interfaces können interpretierte Sprachen (wie z.B. SQL, XPath oder HQL), die häufig in Form eines Strings im Programmcode wieder zu finden sind, abgebildet werden.
Weblinks
- http://www.fluent-interfaces.com
- Bliki von Martin Fowler
- Fluent Interfaces in PHP
- Verkettete Methoden unter PHP
Einzelnachweise
- ↑ Fluent Interfaces (Bliki-Eintrag von Martin Fowler)
- ↑ Method Chaining (Bliki-Eintrag von Martin Fowler)
- ↑ Nested Functions (Bliki-Eintrag von Martin Fowler)
- ↑ Specifications
- ↑ Domain Specific Language (Bliki-Eintrag von Martin Fowler)
- ↑ Evolving an Embedded Domain-Specific Language in Java
- ↑ a b Flüssiger Erbauer
- ↑ Modellgetriebene Realisierung von Fluent Interfaces
Kategorien:- Schnittstelle (Software)
- Entwurfsmuster
- Softwaretechnik
- Systems Engineering
Wikimedia Foundation.