Wenn Daten fließen

Hallo Spaß-Coder.

Wer kennt noch das EVA-Prinzip? Eingabe-Verarbeitung-Ausgabe. Das war eines der ersten Themen im Informatikunterricht. Demnach also kein neuen Prinzip, was deshalb aber nicht weniger interessant ist. Insbesondere, wenn ich an das Single Resposibility Principle (SRP) denke, lässt sich dies gut vereinbaren.

Dazu hat sich insbesondere in der .Net Welt das Flow-Pattern etabliert, welches Events als Kommunikationsmedium zwischen gekapselten Funktionseinheiten (Klassen) nutzt. Hierzu findet ihr auch einige Artikel in der dotnetpro von Ralf Westphal und Stefan Lieser.

Flow-Pattern

Bei der Idee hinter diesem Pattern geht es genau darum: dass Daten fließen. Jede Klasse hat dabei nur genau eine Verantwortlichkeit – bei diesem Pattern geht es kaum noch anders. Die Grundstruktur dazu sieht wie folgt aus:

Der Klassenname „ExtractCharacter“ ist bereits so gewählt, dass die Verantwortlichkeit der Klasse klar ist. Eine Kombination aus Substantiv und Verb schafft hierbei oft Klarheit. Es gibt nur eine einzige Methode mit dem Namen „Process„, welche alle zur Verarbeitung erforderlichen Parameter erhält, jedoch keinen Wert zurückgibt. Stattdessen wird für die Rückgabe des Ergebnisses ein Event mit dem Namen „Progress“ verwendet, da hier im Beispiel typisiert ist und einen „string“ zurückgibt. Die Action stellt hier einen Delegaten dar – einen Funktionszeiger. D.h. hier kann eine Funktion, die einen Parameter vom Typ „string“ erhält, „verbunden“ werden.

Weitere öffentliche Methoden gibt es bei Anwendung des Flow-Patterns nicht, womit es bereits schwieriger wird, der Klasse verschiedene Verantwortlichkeiten zu übertragen.

Die Idee für das Beispiel ist, dass ein bestimmtes Zeichen aus einem angegebenen Text extrahiert wird (mag wenig sinvoll erscheinen, ist aber als Beispiel schön einfach ;-)).  Meine Erwartungen drücke ich als Unit Test aus.

Das zu testende Objekt (subject unter test – „sut“) der Klasse „ExtractCharacter“ wird erstellt. Dann wird das Rückgabe-Event „Progress“ aboniert und per Lambda-Ausdruck wird das Ergebnis „result“ der lokalen Variable „actual“ zugewiesen, um später das Ergebnis vergleichen zu können. Dann wird die Verarbeitung angestoßen per „sut.Process(‚e‘, „Dies ist der gesamte Text.“)“ und die Behauptung aufgestelllt, dass das Ergebnis = „eeeee“ ist.

Dieser Test lässt sich natürlich im derzeitigen Zustand der Klasse „ExtractCharacter“ nicht kompilieren, daher ergänze ich die entsprechende Methodensignatur.

Damit lässt sich der Test zumindest kompilieren. Wie erwartet schlägt der Test fehl und läuft auf rot. Warum? Unsere Klasse macht ja gerade noch recht wenig. Das wollen wir ändern und ich ergänze diese Implementierung:

Da alle gefundenen Zeichen wieder als String zusammengesetz werden sollen, definiere ich einen „StringBuilder“, da dieser deutlich schneller ist, als die Operation „string += string“. Die direkte Stringverknüpfung erzeugt bei jedem Aufruf ein neues Objekt der Klasse String für das Ergebnis und braucht damit mehr Speicher als auch Zeit.

Per LINQ werden jetzt alle Zeichen extrahiert, die im gesetzten Text mit dem gesuchten Zeichen übereinstimmen und davon eine Liste erstellt („stringQuery„). In Zeile 7 wird per Lambda-Ausdruck jedes Zeichen der Ergebnisliste aus dem verhierigen Schritt an den StringBuilder angehangen. Für die Ergebnissrückgabe wird abschließend das öffentliche Event „Progress“ ausgelöst und dabei die Verknüpfung aller gefundenen Zeichen als „string“ mitgegeben.

Eine erneute Ausführung des Test signalisiert: alles grün und damit ist meine formulierte Erwartung erfüllt.

Implementieren wir eine weitere Klasse nach dem Flow-Pattern, welche die Anzahl Zeichen in einem Text zählt und die Anzahl als Ergebnis zurück gibt. Mein Anforderung definiere ich wie folgt:

Wie oben erstelle ich die neue Klasse CountCharacters, aboniere das Ergebnis, führe die Funktion aus und behaupte, das Ergebnis ist 5 (diesmal nicht 42). Meine schnellste Implementierung, bis der Test grün wird, sieht so aus:

Jetzt haben wir zwei sehr kleine Klassen erstellt, die jeweils genau ein Verantwortlichkeit haben. Was machen wir jetzt damit? Hier kommt jetzt die Modularität des Flow-Pattern zur Geltung. Wir können unsere kleinen Komponenten miteinander verknüpfen, sofern wir dabei auf die ein- und ausgehenden Parameter achten. Zur Demonstration erstelle ich einen Intrationstest (klassenübergreifend).

Zunächst werden alle erforderlichen Objekte erstellt. Jetzt kommt die Verknüpfung unserer kleinen Bausteine: die Methode „Process“ des Objekts „countCharacters“ mit dem Eingangsparameter wird gebunden an das Event „Progress“ des Objekts „ExtractCharacter„. Das funktioniert deshalb, weil „ExtractCharacter“ einen Wert vom Typ „string“ zurückgibt.  In Zeile 9 wird wieder das Ergebnis der lokalen Variable „actual“ zugewiesen, damit dieses später verglichen werden kann.

Wenn wir nun in Zeile 11 die Methode „Process“ des Objekts „extractCharacter“ aufrufen, wird dort das Ergebnis (die extrahierten ‚e’s) direkt an „Process“ von „countCharacters“ übergeben.

Wenn ich nun per Flow-Pattern einen ganzen Baukasten von kleinen Funktionseinheiten mit definierten Ein- und Ausgangsparameter erstelle, kann ich diese – je nach Anforderung – flexibel miteinander verbinden. Das ist angewandtes Single Responsibilität Principle und damit echte Wiederverwendbarkeit.

Eine konfigurative Unterstützung beim Verketten von Einheiten bietet eine Flow-Engine wie z.B. von Ralf Westphal. Weitere Informationen und die Engine findet ihr unter http://blog.ralfw.de/2013/01/beispielhafte-nichtbeachtung.html.

 

Zusammenfassung

In diesem Artikel haben wir uns angeschaut, das das Flow-Pattern ist und wie es dabei untersützten kann, Software modular zu gestalten und das SRP ganz leicht anzuwenden. Wie das zum Thema „Objektorientierung wie sie gemeint war“ passt, könnt ihr unter http://geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/oop-as-if-you-meant-it.aspx nachlesen.

 

Viel Spaß beim Ausprobieren und Happy Coding wünschen

eure Spaß-Coder

 

Verwendete Werkzeuge:

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert