|
| 1 | +--- |
| 2 | +shortTitle: Fluent Interface |
| 3 | +category: Behavioral |
| 4 | +language: de |
| 5 | +tag: |
| 6 | + - API design |
| 7 | + - Code simplification |
| 8 | + - Decoupling |
| 9 | + - Object composition |
| 10 | + - Reactive |
| 11 | +--- |
| 12 | + |
| 13 | +## Alternativbezeichnungen |
| 14 | + |
| 15 | +* Fluent API |
| 16 | +* Method Chaining |
| 17 | + |
| 18 | +## Zweck |
| 19 | + |
| 20 | +Primäres Ziel des Fluent-Interface-Pattern ist es, eine gut lesbare sprechende API zur |
| 21 | +Verfügung zu stellen, indem Methodenaufrufe einfach verkettet werden können (Method Chaining). |
| 22 | +Dieser Ansatz ist ideal, um komplexe Objekte schrittweise zu bauen und generell ein angenehmeres |
| 23 | +Programmiererlebnis zu erreichen. |
| 24 | + |
| 25 | +## Detaillierte Erklärung |
| 26 | + |
| 27 | +Vergleichsbeispiel |
| 28 | + |
| 29 | +> Die Bestellung eines frei konfigurierbaren Kaffees in einer Kaffeebar funktioniert ähnlich |
| 30 | +> wie das Fluent-Interface-Pattern. |
| 31 | +> Dabei teilen Sie dem Barista nicht all ihre Wünsche auf einmal mit, sondern nennen schrittweise |
| 32 | +> eine Auswahl nach der anderen, in einem natürlichen Fluss. Sie sagen beispielsweise "Ich möchte |
| 33 | +> einen großen großen Kaffee, mit zwei Espresso-Shots, ohne Zucker, plus Mandelmilch". |
| 34 | +> Diese Aneinanderhängung von Wünschen entspricht dem Verketten von Methodenaufrufen |
| 35 | +> beim Fluent-Interface-Pattern, wodurch der Code zu Objektkonfiguration intuitiv lesbar wird. |
| 36 | +> So wie eine Kaffeekomponente nach der anderen ausgewählt wird, wird im Code eine Methode nach der anderen |
| 37 | +> ausgeführt. |
| 38 | +
|
| 39 | +In einfachen Worten: |
| 40 | + |
| 41 | +> Fluent Interface bietet eine sprechende, leicht lesbare Schnittstelle zum Code. |
| 42 | +
|
| 43 | +Wikipedia sagt: |
| 44 | + |
| 45 | +> In der Softwareentwicklung ist Fluent Interface eine objektorientierte API, deren Design auf |
| 46 | +> ausgiebiger Verkettung von Methoden beruht. Ziel ist verbesserte Lesbarkeit des Codes |
| 47 | +> durch eine domänenspezifische Sprache (DSL, domain specific language). |
| 48 | +
|
| 49 | +Ablaufdiagramm |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | +## Programmbeispiel |
| 54 | + |
| 55 | +Wir wollen Zahlen aus einer Liste nach verschiedenen Kriterien auswählen. Dabei können |
| 56 | +wir die Lesbarkeit des Codes gut mit dem Fluent-Interface-Pattern verbessern. |
| 57 | + |
| 58 | +Der Beispielcode enthält zwei Implementationen eines `FluentIterable`-Interfaces. |
| 59 | + |
| 60 | +```java |
| 61 | +public interface FluentIterable<E> extends Iterable<E> { |
| 62 | + |
| 63 | + FluentIterable<E> filter(Predicate<? super E> predicate); |
| 64 | + |
| 65 | + Optional<E> first(); |
| 66 | + |
| 67 | + FluentIterable<E> first(int count); |
| 68 | + |
| 69 | + Optional<E> last(); |
| 70 | + |
| 71 | + FluentIterable<E> last(int count); |
| 72 | + |
| 73 | + <T> FluentIterable<T> map(Function<? super E, T> function); |
| 74 | + |
| 75 | + List<E> asList(); |
| 76 | + |
| 77 | + static <E> List<E> copyToList(Iterable<E> iterable) { |
| 78 | + var copy = new ArrayList<E>(); |
| 79 | + iterable.forEach(copy::add); |
| 80 | + return copy; |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +`SimpleFluentIterable` betreibt gierige Auswertung und wäre für eine reale Anwendung zu rechenintensiv. |
| 86 | + |
| 87 | +```java |
| 88 | +public class SimpleFluentIterable<E> implements FluentIterable<E> { |
| 89 | + // ... |
| 90 | +} |
| 91 | +``` |
| 92 | +`LazyFluentIterable` wertet erst bei Terminierung der Kette aus. |
| 93 | + |
| 94 | +```java |
| 95 | +public class LazyFluentIterable<E> implements FluentIterable<E> { |
| 96 | + // ... |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +Ihre Verwendung zeigen wir mit einer simplen Zahlenliste, die gefiltert, transformiert |
| 101 | +und zu einer neuen Liste zusammengestellt wird. Das Ergebnis wird anschließend ausgegeben. |
| 102 | + |
| 103 | +```java |
| 104 | +public static void main(String[] args) { |
| 105 | + |
| 106 | + var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); |
| 107 | + |
| 108 | + prettyPrint("The initial list contains: ", integerList); |
| 109 | + |
| 110 | + var firstThreeNegatives = SimpleFluentIterable |
| 111 | + .fromCopyOf(integerList) |
| 112 | + .filter(negatives()) |
| 113 | + .first(3) |
| 114 | + .asList(); |
| 115 | + prettyPrint("The first three negative values are: ", firstThreeNegatives); |
| 116 | + |
| 117 | + |
| 118 | + var lastTwoPositives = SimpleFluentIterable |
| 119 | + .fromCopyOf(integerList) |
| 120 | + .filter(positives()) |
| 121 | + .last(2) |
| 122 | + .asList(); |
| 123 | + prettyPrint("The last two positive values are: ", lastTwoPositives); |
| 124 | + |
| 125 | + SimpleFluentIterable |
| 126 | + .fromCopyOf(integerList) |
| 127 | + .filter(number -> number % 2 == 0) |
| 128 | + .first() |
| 129 | + .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); |
| 130 | + |
| 131 | + |
| 132 | + var transformedList = SimpleFluentIterable |
| 133 | + .fromCopyOf(integerList) |
| 134 | + .filter(negatives()) |
| 135 | + .map(transformToString()) |
| 136 | + .asList(); |
| 137 | + prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); |
| 138 | + |
| 139 | + |
| 140 | + var lastTwoOfFirstFourStringMapped = LazyFluentIterable |
| 141 | + .from(integerList) |
| 142 | + .filter(positives()) |
| 143 | + .first(4) |
| 144 | + .last(2) |
| 145 | + .map(number -> "String[" + number + "]") |
| 146 | + .asList(); |
| 147 | + prettyPrint("The lazy list contains the last two of the first four positive numbers " |
| 148 | + + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); |
| 149 | + |
| 150 | + LazyFluentIterable |
| 151 | + .from(integerList) |
| 152 | + .filter(negatives()) |
| 153 | + .first(2) |
| 154 | + .last() |
| 155 | + .ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number)); |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +Programmausgabe: |
| 160 | + |
| 161 | +``` |
| 162 | +08:50:08.260 [main] INFO com.iluwatar.fluentinterface.app.App -- The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. |
| 163 | +08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The first three negative values are: -61, -22, -87. |
| 164 | +08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The last two positive values are: 23, 2. |
| 165 | +08:50:08.266 [main] INFO com.iluwatar.fluentinterface.app.App -- The first even number is: 14 |
| 166 | +08:50:08.267 [main] INFO com.iluwatar.fluentinterface.app.App -- A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. |
| 167 | +08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. |
| 168 | +08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- Last amongst first two negatives: -22 |
| 169 | +``` |
| 170 | + |
| 171 | +## Verwendung |
| 172 | + |
| 173 | +* Zum Entwurf vielgenutzter APIs, bei denen die Lesbarkeit des sie verwendenden Codes besonders wichtig ist. |
| 174 | +* Zur schrittweisen Konstruktion komplexer Objekte mit intuitivem und weniger fehleranfälligem Code. |
| 175 | +* Zur Verbesserung der Übersichtlichkeit des Codes und Reduktion von Boilerplate-Code, |
| 176 | + speziell bei Konfigurationen und Objektkonstruktion. |
| 177 | + |
| 178 | +## Tutorials |
| 179 | + |
| 180 | +* [An Approach to Internal Domain-Specific Languages in Java (InfoQ)](http://www.infoq.com/articles/internal-dsls-java) |
| 181 | + |
| 182 | +## Reale Anwendungen in Java |
| 183 | + |
| 184 | +* [Java 8 Stream API](http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html) |
| 185 | +* [Google Guava FluentIterable](https://github.com/google/guava/wiki/FunctionalExplained) |
| 186 | +* [JOOQ](http://www.jooq.org/doc/3.0/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder/) |
| 187 | +* [Mockito](http://mockito.org/) |
| 188 | +* [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial) |
| 189 | +* Builder in Bibliotheken wie Apache Camel für den Integrations-Workflow. |
| 190 | + |
| 191 | +## Vor- und Nachteile |
| 192 | + |
| 193 | +Vorteile: |
| 194 | + |
| 195 | +* Signifikant erhöhte Lesbarkeit und Wartbarkeit des Codes |
| 196 | +* Unterstützt die Konstruktion unveränderlicher Objekte, weil die Methoden typischerweise neue Instanzen zurückgeben. |
| 197 | +* Weniger Variablen nötig, da der Kontext durch die Aufrufkette klar wird. |
| 198 | + |
| 199 | +Nachteile: |
| 200 | + |
| 201 | +* Für Neulinge ist der Code weniger intuitiv. |
| 202 | +* Verkettung von Methoden erschwert das Debuggen. |
| 203 | +* Übermäßige Verwendung kann zu komplexem schwer wartbarem Code führen. |
| 204 | + |
| 205 | +## Verwandte Patterns |
| 206 | + |
| 207 | +* [Builder](https://java-design-patterns.com/patterns/builder/): Verwendet oft Fluent Interface zur schrittweisen Konstruktion. Bei Builder geht es um die Konstruktion komplexer Objekte, bei Fluent Interface um die Methodenverkettung. |
| 208 | +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Fluent Interfaces können als spezielle Verwendung von Chain of Responsibility betrachtet werden, wo jede Methode in der Kette eine Teilaufgabe behandelt und ihr Ergebnis an die nächste Methode weiterreicht. |
| 209 | + |
| 210 | +## Quellen |
| 211 | + |
| 212 | +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3UrXkh2) |
| 213 | +* [Domain Specific Languages](https://amzn.to/3R1UYDA) |
| 214 | +* [Effective Java](https://amzn.to/4d4azvL) |
| 215 | +* [Java Design Pattern Essentials](https://amzn.to/44bs6hG) |
| 216 | +* [Fluent Interface (Martin Fowler)](http://www.martinfowler.com/bliki/FluentInterface.html) |
0 commit comments