Rethinking design patterns with Java8 lambda expressions & Stream --Builder pattern -

Introduction

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.

This pattern

Consider the Builder pattern, one of the GoF generation patterns. Please refer to this page for the implementation of the Builder pattern in Java.

Improved fluent interface

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.

problem

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?

Incorporate conditional control

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!");
        });

Incorporate iterative control

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 isBiConsumer <T, U>. The point is to use BiConsumer <T, U>instead ofConsumer because we need to receive and process references to repeating elements (instances of typeT) 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!");
        });

Summary

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

Rethinking design patterns with Java8 lambda expressions & Stream --Builder pattern -
Java8 Lambda Expression & Stream Design Pattern Rethinking --Null Object Pattern -
Java8 Lambda Expression & Stream Design Pattern Rethinking --Chain of Responsibility Pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Command pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Template Method pattern -
Handle exceptions coolly with Java 8 lambda expressions and Stream API
Getting started with Java lambda expressions
Design patterns to enjoy with frequently used Java libraries --Factory pattern
Nowadays Java lambda expressions and Stream API
Design pattern ~ Builder ~
Design pattern (2): Builder
Java Design Patterns
Understand Java 8 lambda expressions
About Java lambda expressions
Explain Java 8 lambda expressions
Builder pattern (Effective Java)
Java Lambda Command Pattern
Java design pattern summary
Use Java lambda expressions outside of the Stream API
Getting Started with Legacy Java Engineers (Stream + Lambda Expression)
Introduction to Design Patterns (Builder)
[Design pattern] Java core library
Java8 stream, lambda expression summary
[Java] Introduction to lambda expressions
[Java] Summary of design patterns
[Java 8] Duplicate deletion (& duplicate check) with Stream
[Introduction to Java] About lambda expressions
About Lambda, Stream, LocalDate of Java8
[Java] Element existence check with Stream
Java beginner design pattern (Factory Method pattern)
The origin of Java lambda expressions
How to use Java lambda expressions
Java8 list conversion with Stream map
I tried to summarize Java lambda expressions
AWS Lambda with Java starting now Part 1
[Design pattern] Common logic with Template Method
Working with huge JSON in Java Lambda
Easy to trip with Java regular expressions