Fluent Interface
Fluent Interfaces [1], stellen in der Softwareentwicklung eine Art Schnittstelle (API) dar, die es ermöglicht beinah in Satzform Funktionalitäten aufzurufen. Der daraus resultierende Programmcode ist besser lesbar und vereinfacht somit das Verständnis über die Intention dessen. Fluent Interfaces können Grammatiken realisieren die dafür sorgen, das 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).
Grundlagen
Als einer 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 Bedigungen getestet wird. Ein weiteres Beispiel wäre das flüssige Erbauen eines Datums.
DateFactory.newDate().year(2009).month(28).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. Methoden-Aufrufe 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änenspezifische 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 werden 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 eigentlich 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 welches einem Objekt vom Typ Date einige Tage und Stunden hinzufügt.
Date date = CalendarUtils
.add(5).days()
.and(10).hours()
.to(date);
Würde man wie im naive 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 bereit hä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() {
...
}
}
...
Bernd Schiffer bezeichnet diese Mediatoren auch als Deskriptoren [7]. Mit obigen 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 beenden 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 zum einen 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 Progammcode überflüssig werden.
- Durch ein satzähnliches Fluent Interface ist 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 was für 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 implemenieren.
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
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