ALLINSIGHT

Home of the AlmostImplementedException

Fluent Interface Pattern

Chris hat bereits einige Design-Pattern wie das Null Object Pattern und das Singleton Pattern erklärt und heute ist es mir eine Freude euch das Fluent Interface Pattern vorzustellen.
Dieses Pattern gibt es bereits seit 2005 und die Erfinder sind Eric Evans und Martin Fowler, wie man in seinem Blog nachlesen kann. Es ist sehr wahrscheinlich das ihr es bereits verwendet habt, vielleicht in Linq (.Net) oder z.B. JBehave (in Java).

Lasst uns, wie immer mit einem einfachen Beispiel anfangen, um die Unterschieden zwischen einem normalen und einem Fluent Interface zu zeigen. Hier sind zwei Aufrufe die im Grunde das selbe bewirken:

Der erste Aufruf verwendet 4 Parameter im Konstruktor. Der zweite verwendet keine Parameter, setzt aber jeden benötigten mit einem anderen Aufruf. Jeder Aufruf gibt die eigenen Instanz zurück, damit der nächste Aufruf wie in einer Kette erfolgen kann.
Der Hauptgrund für die Verwendung dieses Patterns ist der Vorteil der Lesbarkeit. Vor allem bei komplexen APIs mit mehreren optionalen Parametern ist es oft mühselig den richtigen Konstruktor zu finden oder alle benötigten Parameter zu setzen.
In der obigen Kette verwendet ich verschiedenen Wörter wie “with” und “and” um verschiedene Anwendungen zu illustrieren.
Bevor wir zu den Problem bei diesem Ansatz kommen, hier einmal die Implementierung des Fluent Interfaces

Der erste Code-Block ist das Fluent Interface. Wie man sieht, muss jede Methode wie ein Setter implementiert werden und die eigene Instanz zurückgeben. Falls man eine andere Aktion als einen Wert setzen durchführen möchte, geht das natürlich ebenso.
Das zweite Beispiel zeigt einen simplen Konstruktor, der zum selben Ergebnis führt.

An diesem Punkt hören die meisten Beispiele auf und genau hier fangen die Probleme an.
Woher weiß ein Anwender welche Parameter Pflicht sind? Was wenn man sich zwischen 2 Parametern entscheiden muss? Woher weiß die Api das sie bereit zum Ausführen ist?
Mit dem obigen Beispiel ist das alles nicht möglich und die Verwendung von Konstruktor-Parameter für Pflichtwerte und Fluent-Methoden für die optionalen wie es in einigen Beispielen gezeigt wird, ist nicht mal ein Pattern.

Aber nicht aufgeben! Es gibt einen Weg und ihr werdet ihn nicht mögen ( Ich auf jeden Fall nicht). Man muss Interfaces verwenden. Viele Interfaces.
Da wir 3 Pflichtfunktionen und eine optionale (andStopOnError) haben, benötigen wir 4 Interfaces für unsere Kette. Jedes Methode muss ein anderes zurückgeben. Oh und übrigens, einen Konstruktor kann man jetzt natürlich nicht mehr verwenden. Man benötigt stattdessen eine statische Methode die das erste Interface als Rückgabewert verwendet.
Ich erweitere das Beispiel noch um eine Entscheidung zwischen einem FileParser und einem XmlParser und eine execute-Methode.
Zunächst müssen wir jetzt die Interface-Kette definieren.

Die Interface-Kette fängt mit IParser als Startpunkt an. Die statische Methode gibt dieses zurück und erlaubt somit nur einen Aufruf von der Methode withKeywords. Anschließend kann man zwischen withFileParser und withXmlParser wählen, aber nur eine der beiden kann ausgeführt werden, da sie als Rückgabewert das nächste Interface in der Kette zurück geben. Danach kann man withErrorHandler aufrufen und der Initialisierungsprozess ist abgeschlossen.
Nun hat man die Wahl ob man ein execute ausführt oder den optionalen Parameter stopOnError setzt.

Und hier ist die dazugehörige Implementierung. Nicht vergessen das die Klasse jedes Interface implementieren und jede Methode das richtige Interface zurück geben muss.

Ist das nicht schön? Nun kann man die Api verwenden ohne irgendetwas über sie zu wissen. Einen Beispielaufrufe zeige ich am Ende des Posts.

Wie man vielleicht bemerkt ist es ziemlich viel Aufwand ein Fluent Interface zu implementieren. Und es wird noch schlimmer wenn man es nachträglich ändern muss.
Man benötigt viele Interfaces und das Finden von Bugs ist schwerer, da ein CallStack normalerweise Zeilenbasiert arbeitet und der ganze Aufruf ist eine einzige Zeile.
Ein weiterer Dämpfer ist das Problem des “Missbrauchs”. Was passiert wenn jemand das Ergebnis von withKeywords (aus dem Interface IParser2) in einer Variable übernimmt und beide Funktionen (withFileParser und withXmlParser) nacheinander aufruft? Etwas das bei der Verwendung verschiedenen Konstruktoren nicht auftreten kann. Solche Dinge müssen deshalb extra abgefangen werden.

Fazit:
Meiner Meinung nach sind Fluent Interface ein toller Weg um viel Zeit zu verbraten und andere Entwickler zu beeindrucken, aber man kann sie nicht verwenden wenn man viele Parameter anbieten will oder das Interface sich noch ändern kann.
Positiv ist aber, dass das Interface sich selber dokumentiert und für Nicht-Entwickler leichter zu lesen ist.

Share :

Leave a Reply