Skip to content

Commit ab1a30b

Browse files
committed
docs: improve fluent interface
1 parent 6d38534 commit ab1a30b

File tree

3 files changed

+95
-66
lines changed

3 files changed

+95
-66
lines changed

Diff for: fluentinterface/README.md

+93-64
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
---
22
title: Fluent Interface
3-
category: Functional
3+
category: Behavioral
44
language: en
55
tag:
6-
- Reactive
6+
- API design
7+
- Code simplification
8+
- Decoupling
9+
- Object composition
10+
- Reactive
711
---
812

13+
## Also known as
14+
15+
* Fluent API
16+
* Method Chaining
17+
918
## Intent
1019

11-
A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific
12-
language. Using this pattern results in code that can be read nearly as human language.
20+
A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language.
1321

1422
## Explanation
1523

@@ -24,18 +32,15 @@ A fluent interface can be implemented using any of
2432

2533
Real world example
2634

27-
> We need to select numbers based on different criteria from the list. It's a great chance to
28-
> utilize fluent interface pattern to provide readable easy-to-use developer experience.
35+
> We need to select numbers based on different criteria from the list. It's a great chance to utilize fluent interface pattern to provide readable easy-to-use developer experience.
2936
3037
In plain words
3138

3239
> Fluent Interface pattern provides easily readable flowing interface to code.
3340
3441
Wikipedia says
3542

36-
> In software engineering, a fluent interface is an object-oriented API whose design relies
37-
> extensively on method chaining. Its goal is to increase code legibility by creating a
38-
> domain-specific language (DSL).
43+
> In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL).
3944
4045
**Programmatic Example**
4146

@@ -70,72 +75,72 @@ The `SimpleFluentIterable` evaluates eagerly and would be too costly for real wo
7075

7176
```java
7277
public class SimpleFluentIterable<E> implements FluentIterable<E> {
73-
...
78+
// ...
7479
}
7580
```
7681

7782
The `LazyFluentIterable` is evaluated on termination.
7883

7984
```java
8085
public class LazyFluentIterable<E> implements FluentIterable<E> {
81-
...
86+
// ...
8287
}
8388
```
8489

8590
Their usage is demonstrated with a simple number list that is filtered, transformed and collected. The
86-
result is printed afterwards.
91+
result is printed afterward.
8792

8893
```java
89-
var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68);
90-
91-
prettyPrint("The initial list contains: ", integerList);
92-
93-
var firstFiveNegatives = SimpleFluentIterable
94-
.fromCopyOf(integerList)
95-
.filter(negatives())
96-
.first(3)
97-
.asList();
98-
prettyPrint("The first three negative values are: ", firstFiveNegatives);
99-
100-
101-
var lastTwoPositives = SimpleFluentIterable
102-
.fromCopyOf(integerList)
103-
.filter(positives())
104-
.last(2)
105-
.asList();
106-
prettyPrint("The last two positive values are: ", lastTwoPositives);
107-
108-
SimpleFluentIterable
109-
.fromCopyOf(integerList)
110-
.filter(number -> number % 2 == 0)
111-
.first()
112-
.ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber));
113-
114-
115-
var transformedList = SimpleFluentIterable
116-
.fromCopyOf(integerList)
117-
.filter(negatives())
118-
.map(transformToString())
119-
.asList();
120-
prettyPrint("A string-mapped list of negative numbers contains: ", transformedList);
121-
122-
123-
var lastTwoOfFirstFourStringMapped = LazyFluentIterable
124-
.from(integerList)
125-
.filter(positives())
126-
.first(4)
127-
.last(2)
128-
.map(number -> "String[" + valueOf(number) + "]")
129-
.asList();
130-
prettyPrint("The lazy list contains the last two of the first four positive numbers "
131-
+ "mapped to Strings: ", lastTwoOfFirstFourStringMapped);
132-
133-
LazyFluentIterable
134-
.from(integerList)
135-
.filter(negatives())
136-
.first(2)
137-
.last()
138-
.ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number));
94+
var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68);
95+
96+
prettyPrint("The initial list contains: ", integerList);
97+
98+
var firstFiveNegatives = SimpleFluentIterable
99+
.fromCopyOf(integerList)
100+
.filter(negatives())
101+
.first(3)
102+
.asList();
103+
prettyPrint("The first three negative values are: ", firstFiveNegatives);
104+
105+
106+
var lastTwoPositives = SimpleFluentIterable
107+
.fromCopyOf(integerList)
108+
.filter(positives())
109+
.last(2)
110+
.asList();
111+
prettyPrint("The last two positive values are: ", lastTwoPositives);
112+
113+
SimpleFluentIterable
114+
.fromCopyOf(integerList)
115+
.filter(number -> number % 2 == 0)
116+
.first()
117+
.ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber));
118+
119+
120+
var transformedList = SimpleFluentIterable
121+
.fromCopyOf(integerList)
122+
.filter(negatives())
123+
.map(transformToString())
124+
.asList();
125+
prettyPrint("A string-mapped list of negative numbers contains: ", transformedList);
126+
127+
128+
var lastTwoOfFirstFourStringMapped = LazyFluentIterable
129+
.from(integerList)
130+
.filter(positives())
131+
.first(4)
132+
.last(2)
133+
.map(number -> "String[" + valueOf(number) + "]")
134+
.asList();
135+
prettyPrint("The lazy list contains the last two of the first four positive numbers "
136+
+ "mapped to Strings: ", lastTwoOfFirstFourStringMapped);
137+
138+
LazyFluentIterable
139+
.from(integerList)
140+
.filter(negatives())
141+
.first(2)
142+
.last()
143+
.ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number));
139144
```
140145

141146
Program output:
@@ -158,8 +163,9 @@ Last amongst first two negatives: -22
158163

159164
Use the Fluent Interface pattern when
160165

161-
* You provide an API that would benefit from a DSL-like usage.
162-
* You have objects that are difficult to configure or use.
166+
* Designing APIs that are heavily used and where readability of client code is of high importance.
167+
* Building complex objects step-by-step, and there is a need to make the code more intuitive and less error-prone.
168+
* Enhancing code clarity and reducing the boilerplate code, especially in configurations and object-building scenarios.
163169

164170
## Known uses
165171

@@ -168,10 +174,33 @@ Use the Fluent Interface pattern when
168174
* [JOOQ](http://www.jooq.org/doc/3.0/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder/)
169175
* [Mockito](http://mockito.org/)
170176
* [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial)
177+
* Builders in libraries like Apache Camel for integration workflows.
178+
179+
## Consequences
180+
181+
Benefits:
182+
183+
* Improved code readability and maintainability.
184+
* Encourages building immutable objects since methods typically return new instances.
185+
* Reduces the need for variables as the context is maintained in the chain.
186+
187+
Trade-offs:
188+
189+
* Can lead to less intuitive code for those unfamiliar with the pattern.
190+
* Debugging can be challenging due to the chaining of method calls.
191+
* Overuse can lead to complex and hard-to-maintain code structures.
192+
193+
## Related Patterns
194+
195+
* [Builder](https://java-design-patterns.com/patterns/builder/): Often implemented using a Fluent Interface to construct objects step-by-step. The Builder Pattern focuses on constructing complex objects, while Fluent Interface emphasizes the method chaining mechanism.
196+
* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Fluent Interfaces can be seen as a specific utilization of the Chain of Responsibility, where each method in the chain handles a part of the task and then delegates to the next method.
171197

172198
## Credits
173199

174200
* [Fluent Interface - Martin Fowler](http://www.martinfowler.com/bliki/FluentInterface.html)
175201
* [Evolutionary architecture and emergent design: Fluent interfaces - Neal Ford](http://www.ibm.com/developerworks/library/j-eaed14/)
176202
* [Internal DSL](http://www.infoq.com/articles/internal-dsls-java)
177203
* [Domain Specific Languages](https://www.amazon.com/gp/product/0321712943/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=0321712943&linkId=ad8351d6f5be7d8b7ecdb650731f85df)
204+
* [Effective Java](https://amzn.to/4d4azvL)
205+
* [Java Design Pattern Essentials](https://amzn.to/44bs6hG)
206+
* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3UrXkh2)

Diff for: fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* {@link SimpleFluentIterable} evaluates eagerly and would be too costly for real world
4343
* applications. The {@link LazyFluentIterable} is evaluated on termination. Their usage is
4444
* demonstrated with a simple number list that is filtered, transformed and collected. The result is
45-
* printed afterwards.
45+
* printed afterward.
4646
*/
4747
@Slf4j
4848
public class App {

Diff for: fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void testForEach() {
191191
}
192192

193193
@Test
194-
void testSpliterator() throws Exception {
194+
void testSpliterator() {
195195
final var integers = List.of(1, 2, 3);
196196
final var split = createFluentIterable(integers).spliterator();
197197
assertNotNull(split);

0 commit comments

Comments
 (0)