With the introduction of lambda expressions and the Stream API in Java 8, the Java language has finally brought a paradigm of functional programming. As a result, the design joseki
is also changing.
For Stream API and lambda expressions, I think you should learn from O'Reilly's good book "Functional Programming with Java". , Chapter 4 "Designing with Lambda Expressions" in this book introduces new design ideas that utilize lambda expressions.
In this article, the author, inspired by the book above, examines and considers new ways to implement design patterns using lambda expressions and Streams.
Consider the Builder
pattern, one of the GoF generation patterns.
Please refer to this page for the implementation of the Builder
pattern in Java.
The following techniques are introduced in "Functional Programming with Java".
--Make the constructor private to prevent it from being instantiated directly
--Instead, prepare a static method that takes a function of type Consumer <T>
as an argument.
FluentBuilder
public class MailBuilder {
private String fromAddress = "";
private String toAddress = "";
private List<String> ccAddresses = new ArrayList<>();
private String subject = "";
private String body = "";
private MailBuilder() {
}
public MailBuilder from(String address) {
this.fromAddress = address;
return this;
}
public MailBuilder to(String address) {
this.toAddress = address;
return this;
}
public MailBuilder cc(String address) {
this.ccAddresses.add(address);
return this;
}
public MailBuilder subject(String subject) {
this.subject = subject;
return this;
}
public MailBuilder body(String body) {
this.body = body;
return this;
}
private void doSend() {
StringBuilder sb = new StringBuilder();
sb.append("TO:").append(toAddress).append("\r\n");
if (!ccAddresses.isEmpty()) {
sb.append("CC:").append(String.join(",", ccAddresses)).append("\r\n");
}
sb.append("FROM:").append(fromAddress).append("\r\n");
sb.append("SUBJECT:").append(subject).append("\r\n");
sb.append("BODY:").append(body).append("\r\n");
System.out.println(sb.toString());
}
public static void send(final Consumer<MailBuilder> consumer) {
final MailBuilder mailer = new MailBuilder();
consumer.accept(mailer);
mailer.doSend();
}
The usage example of the above Builder
is as follows.
FluentBuilder-Usage
MailBuilder.send(mailer -> {
mailer.from("[email protected]")
.to("[email protected]")
.subject("Greeting")
.body("Hello, Mr. President!");
});
The advantages compared to the builder that assembles with the conventional fluent interface
are as follows.
――Because it does not use the new
keyword, it is more readable and fluent.
--The reference scope of the instance of Builder
is limited to the code block passed to the static method.
One of the inconveniences of using a Builder
of type fluent interface
is when you want to switch method calls depending on conditions, or when you want to call methods repeatedly.
Due to the Java language specifications, control syntax cannot be embedded in a method chain connected by dots .
, so the method chain is divided in the middle and controlled using if and for statements, resulting in I'm no longer fluent
.
Can't you solve this problem well?
Add a method that calls Consumer <T>
only if the conditional expression is true
.
MoreFluentBuilder
public MailBuilder doIf(boolean condition, final Consumer<MailBuilder> consumer) {
if (condition) {
consumer.accept(this);
}
return this;
}
Although the lambda expression would be nested, we were able to incorporate conditional control without breaking the method chain.
MoreluentBuilder-Usage
MailBuilder.send(mailer -> {
mailer.from("[email protected]")
.to("[email protected]")
.doIf(someCondition(), m -> m.cc("[email protected]"))
.subject("Greeting")
.body("Hello, Mr. President!");
});
This time it's a bit complicated because we need to take two functional types as arguments.
First, it receives the object to be repeated in the ʻIterable type. And the second argument is
BiConsumer <T, U>. The point is to use
BiConsumer <T, U>instead of
Consumer because we need to receive and process references to repeating elements (instances of type
T) and
Builder` instances. ..
MoreFluentBuilder2
public <T> MailBuilder foreach(Iterable<T> iterable, final BiConsumer<MailBuilder, T> consumer) {
iterable.forEach(t -> consumer.accept(this, t));
return this;
}
I was able to embed iterative controls without breaking the method chain, as shown below.
MoreFluentBuilder2-Usage
final List<String> ccAddresses = Arrays.asList("[email protected]", "[email protected]");
MailBuilder.send(mailer -> {
mailer.from("[email protected]")
.to("[email protected]")
.foreach(ccAddresses, (m, ccAddress) -> m.cc(ccAddress))
.subject("Greeting")
.body("Hello, Mr. President!");
});
Making good use of lambda expressions allows you to write cleaner, clearer code.
This time, I examined how to make the implementation of the Builder
pattern of the fluent interface
type more fluent.
Recommended Posts