Java8 Lambda Expression & Stream Design Pattern Rethinking --Null Object Pattern -

Introduction

In this series, we will consider how to implement design patterns using the lambda expression / Stream API introduced in Java 8. The sample code is uploaded to GitHub.

This pattern

I mentioned the Null Object pattern in the previous article, but I've omitted the explanation, so I'll cover it this time. The Null Object pattern is a safe code that does not require null checking by referencing aobject that implements aspecific interface instead of a null reference but does nothing (has no side effects). This is a writing technique.

Traditional implementation

Assume a class that delegates processing to the following interfaces.

Discount.java


public interface Discount {
    Integer calcDiscount(Integer totalAmount);
}

Assuming that the reference to the instance of the implementation class of Dicsount is given by the constructor or setter, the following null check will be required in consideration of the case of null when using it.

OrderService.java(1)


    private Discount discount;

    public Integer getPrice(Integer amount) {
        if (discount != null) {
            amount -= discount.calcDiscount(amount);
        }
        return amount;
    }

If you use the discount variable in multiple places, it's tedious to do a null check each time. Therefore, set an instance that does nothing (to be exact, processing so as not to affect the processing on the user side), that is, Null Object as follows. Null Object is often defined as an inner class or an anonymous inner class as shown below.

OrderService.java(2)


    private Discount discount2 = new Discount() {
        @Override
        public Integer calcDiscount(Integer totalAmount) {
            return 0;
        }
    };

    public Integer getPrice2(Integer amount) {
        amount -= discount2.calcDiscount(amount);
        return amount;
    }

By the way, @FunctionalInterface is not added to the Discount interface, but it can be treated as a functional interface because it has only one method. That is, you can also define a Null Object using a lambda expression like this:

OrderService.java(3)


    private Discount discount3 = amount -> 0;

    public Integer getPrice3(Integer amount) {
        amount -= discount3.calcDiscount(amount);
        return amount;
    }

Functional Null Object

The following sample is an example of a utility that manipulates dates.

DateTimeBuilder.java


public class DateTimeBuilder {
    private LocalDateTime base;
    private Function<Temporal, Temporal> func;

    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        func = t -> t;
    }

    private void compose(TemporalAdjuster next) {
        func = func.andThen(t -> next.adjustInto(t));
    }

    public DateTimeBuilder nextMonth() {
        compose(t -> t.plus(1, ChronoUnit.MONTHS));
        return this;
    }

    public DateTimeBuilder lastDay() {
        compose(TemporalAdjusters.lastDayOfMonth());
        return this;
    }

    public LocalDateTime get() {
        return (LocalDateTime) func.apply(base);
    }

    public static DateTimeBuilder of(LocalDateTime base) {
        DateTimeBuilder builder = new DateTimeBuilder(base);
        return builder;
    }
}

The usage example is as follows. The following code finds the last day of the month following the date given as an argument (2017/1/17).

Usage


        LocalDateTime base = LocalDateTime.of(2017, 1, 17, 0, 0);
        LocalDateTime dt = DateTimeBuilder.of(base)
                .nextMonth()
                .lastDay()
                .get();
        assertEquals(LocalDateTime.of(2017, 2, 28, 0, 0), dt);

In DateTimeBuilder, every timenextMonth ()orlastDay ()is called, the function that converts the corresponding date is synthesized into the instance variable func, and finally get () is The composition function is applied when it is called. The following code is synthesizing, but at this time, the Null Object pattern is introduced so that it is not necessary to consider that NullPointerException does not occur when func is null. Will be the motivation for.

#compose method


    private void compose(TemporalAdjuster next) {
        func = func.andThen(t -> next.adjustInto(t));
    }

So, in the constructor, set func to a function that does nothing. The fact that the Function <Temporal, Temporal> type does nothing means that the object of the Temporal type received as an argument is returned as it is (passed from right to left). That is t-> t when written in a lambda expression.

Function corresponding to NullObject


    private Function<Temporal, Temporal> func;

    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        func = t -> t;
    }

Function # identity is the identity element

The lambda expression t-> t mentioned above should use the static method ʻidentity ()defined in thejava.util.Function` interface.

Function#identity()Use of


    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        //func = t -> t;
        func = Function.identity();
    }

Function # identity () returns a function that does nothing, that is, a function that returns the input arguments as they are. This function can be combined with another conversion function (let's call it g) from the left or from the right to get a composition function that returns a result equal to g. Mathematically speaking, it can be said that this is the identity element in the systemFunction <Temporal, Temporal>. Actually, the English translation of the identity element is ʻidentity element`.

Summary

When synthesizing a function, using Function # identity () as the identity element as the initial value seems to reduce complicated null check code and improve the visibility of the code.

Recommended Posts

Java8 Lambda Expression & Stream Design Pattern Rethinking --Null Object Pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Command pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Template Method pattern -
Rethinking design patterns with Java8 lambda expressions & Stream --Builder pattern -
Java8 stream, lambda expression summary
Java design pattern
[Java] Lambda expression
Java lambda expression
java neutral lambda expression 1
Java lambda expression variations
Java 8 lambda expression Feature
java lambda expression memo
Studying Java 8 (lambda expression)
Java lambda expression again
Java Lambda Command Pattern
Java design pattern summary
Getting Started with Legacy Java Engineers (Stream + Lambda Expression)
[Java] Functional interface / lambda expression
[Design pattern] Java core library
About Lambda, Stream, LocalDate of Java8
Java basic learning content 9 (lambda expression)
What is a lambda expression (Java)
Java beginner design pattern (Factory Method pattern)
Now let's recap the Java lambda expression
Nowadays Java lambda expressions and Stream API
[Java] Convert Object type null to String type
I've reviewed Java's lambda expression, stream API, six months after I started Java.
How to use Java API with lambda expression
Java8 to start now ~ forEach and lambda expression ~