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.
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 a
specific interface instead of a null reference but does nothing (has no side effects). This is a writing technique.
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;
}
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 elementThe lambda expression t-> t
mentioned above should use the static method ʻidentity ()defined in the
java.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`.
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