Sei sulla pagina 1di 1

Sign in Get started

ARCHIVE WRIT E FOR US S T YLE GUIDE ABOUT JOB BOARD

You have 2 free stories left this month. Sign up and get an extra one for free.

How Fluent Interfaces Can Lead to


More Meaningful Code
Simpler and safer work ows for object creation and more
Ben Weidig Follow
Apr 20 · 5 min read

Photo by 贝莉儿 DANIST on Unsplash

Martin Fowler, who coined the term Fluent Interfaces 15 years ago,
famously wrote in his book Refactoring: Improving the Design of Existing
Code:

“Any fool can write code that a computer can Top highlight

understand. Good programmers write code that


humans can understand.”

Software development is about communication. The eventual recipient


might be a compiler or interpreter, running our code as a series of ones
and zeros. Computers don’t care about the readability of source code,
but the other participants in the communication do: humans.

It’s about one developer communicating with another one. Or with


ourselves in the future, when our code appears to us like it was written
by someone else.

I’ve written about the importance of the visual representation of code


before, this article will highlight the concept of fluent interfaces and how
we can incorporate it into our codebases for instance creation. But it will
also show the downsides of using this particular API style.

All code examples will be in Java, or reference Java frameworks. But the
general principles of fluent interfaces apply to any other language, too.

. . .

Fluent Interfaces
As with many other design patterns, the core concept can be summarized
in a short sentence:

A fluent interface is designed to mimic a Domain-Specific-Language


(DSL) by relying heavily on method-chaining.

In object-oriented programming, method-chaining is a way of invoking


multiple method-calls successively. This allows us to write our code in a
fluent, more straightforward way, and reduce visual noise.

To achieve the ability to actually use method-chaining, methods can’t


return void or another arbitrary value. They must return the most
logical value for the given context.

There are three ways in which we can have more fluent code.

The mutable way


Each setter or related method of an instance mutates the instance itself
and returns this .

This way of fluency isn’t appropriate for instance creation, at least in my


opinion. We gain method-chaining, but that’s all. It should be reserved
for non-POJO types, e.g., fluent interfaces for workflows.

The immutable way


Instead of returning the instance itself, a new instance representing the
mutated state is returned. A prime example of this behavior is the
java.time API, or java.math.BigDecimal :

1 // CORRECT
2 BigDecimal basePrice = new BigDecimal("9.99");
3 BigDecimal withTaxes = basePrice.multiply(new BigDecimal("1.19"));
4
5 // INVALID
6 BigDecimal basePrice = new BigDecimal("9.99");
7 basePrice.multiply(new BigDecimal("1.19")); // won't modify basePrice

medium-2020-fluent-interfaces-01.java hosted with ❤ by GitHub view raw

With intermediary builder


The type itself doesn’t have to be fluent. We could use an additional
fluent auxiliary type for instance creation, e.g., a fluent builder.

In Java, we can use builders to get around the lack of named parameters.

. . .

How to Create a Fluent Builder


We start with a type describing a developer:

1 class Developer {
2 private String email;
3 private String name;
4 private List<String> languages;
5
6 public String getEmail() {
7 return this.email;
8 }
9
10 public void setEmail(String email) {
11 this.email = email;
12 }
13
14 public String getName() {
15 return this.name;
16 }
17
18 public void setName(String name) {
19 this.name = name;
20 }
21
22 public List<String> getLanguages() {
23 return this.languages;
24 }
25
26 public void setLanguages(List<String> languages) {
27 this.languages = languages;
28 }
29 }

medium-2020-fluent-interfaces-02.java hosted with ❤ by GitHub view raw

A simple POJO, just storing some data, without any extra functionality.

Creating a simple builder


To improve instance creation, we need an additional builder type:

1 class DeveloperBuilder {
2 private String email;
3 private String name;
4
5 private final List<String> languages = new ArrayList<>();
6
7 public DeveloperBuilder email(String email) {
8 if (email == null || email.length() == 0) {
9 throw new IllegalArgumentException("email musn't be null/empty");
10 }
11
12 this.email = email;
13
14 return this;
15 }
16
17 public DeveloperBuilder name(String name) {
18 this.name = name;
19 return this;
20 }
21
22 public DeveloperBuilder language(String lang) {
23 if (lang == null || lang.length() == 0) {
24 throw new IllegalArgumentException("lang musn't be null/empty");
25 }
26
27 if (this.languages.contains(lang)) {
28 return this;
29 }
30
31 this.languages.add(lang);
32
33 return this;
34 }
35
36 public Developer build() {
37 if (email == null || email.length() == 0) {
38 throw new IllegalStateException("email is a required field");
39 }
40
41 if (this.language.isEmpty()) {
42 throw new IllegalStateException("at least one language must be added");
43 }
44
45 Developer developer = new Developer();
46 developer.setEmail(this.email);
47 developer.setName(this.name);
48 developer.setLanguages(this.languages);
49
50 return developer;
51 }
52 }

medium-2020-fluent-interfaces-03.java hosted with ❤ by GitHub view raw

By using the builder pattern, we still have our simple POJO type, but now
instance creation can also have validation during preparing the builder
and at creating the actual instance.

Using the builder

1 Developer validDev =
2 new DeveloperBuilder().email("ben@example.com")
3 .name("Ben Weidig")
4 .language("Java")
5 .language("Swift")
6 .language("Golang")
7 .build();
8
9 Developer invalidDev1 =
10 new DeveloperBuilder().name("Ben Weidig")
11 .language("Java")
12 .language("Swift")
13 .language("Golang")
14 // throws IllegalStateException
15 .build();
16
17 Developer invalidDev2 =
18 new DeveloperBuilder().email("ben@example.com")
19 .name("Ben Weidig")
20 // throws IllegalArgumentException
21 .language(null)
22 .language("Swift")
23 .language("Golang")
24 .build();

medium-2020-fluent-interfaces-04.java hosted with ❤ by GitHub view raw

The fluent builder separates the code for construction and


representation.

We gained a more understandable way of creating instances, with


additional control, like validation, during the construction process.
Especially complex types can be improved with builders.

But we can improve our builder, and also the corresponding type, even
more.

Improving the builder with immutability


An often-used design is to make the builder a nested class of its
corresponding type. This also means giving up a pure POJO, but gaining
an immutable type instead, which is a great bargain in my opinion:

1 public class Developer {


2 private final String email;
3 private final String name;
4 private final List<String> languages;
5
6 // private constructor, so we are forced to use the builder
7 private Developer(String email,
8 String name,
9 List<String> languages) {
10 this.email = email;
11 this.name = name;
12 // make languages collection immutable, too
13 this.languages = Collections.unmodifiableList(languages);
14 }
15
16 public String getEmail() {
17 return this.email;
18 }
19
20 public String getName() {
21 return this.name;
22 }
23
24 public List<String> getLanguages() {
25 return this.languages;
26 }
27
28 public static Builder builder() {
29 return new Builder();
30 }
31
32 public static class Builder {
33 private String email;
34 private String name;
35 private final List<String> languages = new ArrayList<>();
36
37 // builder needs to be created with static method on actual type
38 private Builder() { }
39
40 public Builder email(String email) {
41 if (email == null || email.length() == 0) {
42 throw new IllegalArgumentException("email musn't be null/empty");
43 }
44
45 this.email = email;
46
47 return this;
48 }
49
50 public Builder name(String name) {
51 this.name = name;
52 return this;
53 }
54
55 public Builder language(String lang) {
56 if (lang == null || lang.length() == 0) {
57 throw new IllegalArgumentException("lang musn't be null/empty");
58 }
59
60 if (this.languages.contains(lang)) {
61 return this;
62 }
63
64 this.languages.add(lang);
65 return this;
66 }
67
68 public Developer build() {
69 if (this.email == null || this.email.length() == 0) {
70 throw new IllegalStateException("email is a required field");
71 }
72
73 if (this.languages.isEmpty()) {
74 throw new IllegalStateException("at least one language must be added");
75 }
76
77 return new Developer(this.email,
78 this.name,
79 this.languages);
80 }
81 }
82 }

medium-2020-fluent-interfaces-05.java hosted with ❤ by GitHub view raw

The Developer type is now wholly immutable, and can only be created by
using the builder.

1 Developer valid = Developer.builder()


2 .email("ben@example.com")
3 .name("Ben Weidig")
4 .language("Java")
5 .language("Swift")
6 .language("Golang")
7 .build();

medium-2020-fluent-interfaces-06.java hosted with ❤ by GitHub view raw

Immutable builder generation


Let’s be honest… that’s still a lot of additional code. And we would need
to write it for every POJO-like type.

Instead of doing it ourselves, we can use frameworks like Immutables:

1 @Value.Immutable
2 public interface Developer {
3 String getEmail();
4
5 @Nullable
6 String getName();
7
8 @Value.Default
9 default List<String> getLanguages() {
10 return Collections.emptyList();
11 }
12 }

medium-2020-fluent-interfaces-07.java hosted with ❤ by GitHub view raw

Thanks to annotation processing, our builders are generated for use,


including validation, more builder methods, easy copying, and a lot more
features than we could ever do ourselves:

1 Developer dev = ImmutableDeveloper.builder()


2 .email("ben@example.com")
3 .name("Ben Weidig")
4 .addLanguages("Java")
5 .addLanguages("Swift", "Golang")
6 .build();

medium-2020-fluent-interfaces-08.java hosted with ❤ by GitHub view raw

. . .

Fluent Interfaces for Workflows


Even though this article is mainly about fluent builders, it’s not the only
way to use fluent interfaces. They are widely adopted for any kind of
multi-step workflow.

An excellent example of fluent API design is the Java Streams API. We


can reduce a lot of verbosity by method-chaining Stream-related calls
together, instead of using the “traditional” means of iterating over a
collection:

1 // "TRADITIONAL" WAY OF ITERATING


2
3 List<Book> books = ...;
4 List<String> result = new ArrayList<>();
5
6 for (Book book : Books) {
7 if (book.getPublishYear() != 2020) {
8 continue;
9 }
10
11 if (book.getGenre() != Genre.SCIENCE_FICTION) {
12 continue;
13 }
14
15 Author author = book.getAuthor();
16 String sortableName =
17 String.format("%s, %s",
18 author.getLastName(),
19 author.getFirstName());
20
21 result.add(sortableName);
22
23 if (result.size() == 5) {
24 break
25 }
26 }
27
28 Collections.sort(result);

medium-2020-fluent-interfaces-09.java hosted with ❤ by GitHub view raw

A lot of code for getting the names of the first five science fiction authors
of 2020 in our collection.

Let’s make this call fluent with streams:

1 // FLUENT WITH STREAM


2
3 List<Book> books = ...;
4
5 List<String> result =
6 books.stream()
7 .filter(book -> book.getPublishYear() == 2020)
8 .filter(book -> book.getGenre() == Genre.SCIENCE_FICTION)
9 .limit(5)
10 .map(Book::getAuthor)
11 .map(author -> String.format("%s, %s",
12 author.getLastName(),
13 author.getFirstName()))
14 .sorted()
15 .collect(Collectors.toList());

medium-2020-fluent-interfaces-10.java hosted with ❤ by GitHub view raw

If we include the formatting and brackets, we managed to reduce 20


lines of code down to ~ten lines.

The reduced clutter is great, but in my opinion, the improved


comprehensibility is more important. The concise names of the Stream
operations convey their intent clearly.

. . .

Conclusion
Fluent interfaces are great for consumption, but not for creation.

Creating a more DSL-like API helps developers to consume it with less


verbose, more comprehensible code.

We get more control over how we create an object, even split it into
multiple steps. Validation can be integrated at every step. And it helps to
separate the code for creation from the actual representation.

But creating an enjoyable, fluent API isn’t an easy task. We need


additional auxiliary types, which means a lot of extra, and often
repeated, code.

Another downside is creating the possibility for more runtime-errors. If


our type needs all its required parameters in the constructor, we would
directly see what’s required. With a builder, we might miss them and
won’t realize until runtime.

Debugging can also be harder if our IDE doesn’t support breakpoints at


different points in the chain. This can be often overcome by breaking the
chain into multiple lines, which coincidentally improves readability, too.

At my company, we started using a fluent builder last year, thanks to the


Immutables framework. We started by using it only for new types, but
also replaced older types over time, if appropriate.

Sometimes it wasn’t easy to integrate immutable types into the existing


code, but it gave us a chance to reevaluate our API design and improve
on it.

. . .

Resources
FluentInterface (Martin Fowler)

Fluent Interface (Wikipedia)

Immutables framework

Implementing the builder pattern in Java 8 (Vogella)

Functional programming with Java: Immutability

Functional programming with Java: Streams

Signal-to-noise ratio: Codeburst

T hanks to Zack Shapiro.

Software Development Programming Software Engineering Java Android

74 claps

WRIT T EN BY

Ben Weidig Follow

Software developer, entrepeneur, blogger. Mostly Java,


sometimes Swift, Golang, Bash and all the other fun stu .

Better Programming Follow

Advice for programmers.

Write the rst response

More From Medium

Greed and climate- How to Conquer Bliss Is Bucolic in 5 things that UX Design
change denial: Pay no Rejection Canada’s New Brunswick should learn from
attention to the man James Malcher Mai Mislang gaming industry
behind the curtain Jerzy Bartoszewicz in
Carl Pope 10Clouds

Mexican X-plainer: Are the ‘Innocent Babies’ How To Survive How T he Media Made
Tacos, Not T lahcos Being Saved Today Quarantine With T he Anthony Scaramucci T he
David Bowles Potentially the Help Of Some Old Jokes Annoying “Moocher”
‘Dangerous Criminals’ of John DeVore in Humungus Ezinne Ukoha
the Future?
Karen Banes in Fearless She
Wrote

Discover Medium Make Medium yours Become a member


Welcome to a place where words matter. On Medium, smart Follow all the topics you care about, and we’ll deliver the Get unlimited access to the best stories on Medium — and
voices and original ideas take center stage - with no ads in best stories for you to your homepage and inbox. Explore support writers while you’re at it. Just $5/month. Upgrade
sight. Watch

About Help Legal

Potrebbero piacerti anche