Java8 Lambda expression & Stream design pattern reconsideration --Template Method pattern -

Introduction

We will consider a new implementation method of design patterns using the Stream API, which is a lambda expression introduced in Java8. In the previous article (http://qiita.com/yonetty/items/a6afb566c4079a6cf34c), we considered how to implement the Builder pattern using lambda expressions.

This theme

This time, let's think about the Template Method pattern based on the FizzBuzz problem.

Conventional mounting method

In order to reuse the common behavior (logic), define the template method in the abstract class with final, and define the variable part as the hook method ʻabstract. By implementing the hook method in the concrete class, it is possible to express individual behavior while retaining common behavior. (Defining a template method final` prohibits subclasses from overriding common behavior)

The abstract class looks like this: count (int, int) is the template method.

AbstractCounter.java


public abstract class AbstractCounter {

    public final void count(final int from, final int to) {
        for (int i = from; i <= to; i++) {
            final String expression;
            if (condition1(i)) {
                expression = condition2(i) ? onBoth(i) : onCondition1(i);
            } else {
                expression = condition2(i) ? onCondition2(i) : String.valueOf(i);
            }
            System.out.print(expression + ",");
        }
    }

    abstract boolean condition1(int num);

    abstract boolean condition2(int num);

    abstract String onCondition1(int num);

    abstract String onCondition2(int num);

    abstract String onBoth(int num);
}

An implementation of FizzBuzz. Override 5 ʻabstract` methods.

FizzBuzzCounter.java


public class FizzBuzzCounter extends AbstractCounter {

    @Override
    boolean condition1(int num) {
        return num % 3 == 0;
    }

    @Override
    boolean condition2(int num) {
        return num % 5 == 0;
    }

    @Override
    String onCondition1(int num) {
        return "Fizz";
    }

    @Override
    String onCondition2(int num) {
        return "Buzz";
    }

    @Override
    String onBoth(int num) {
        return "FizzBuzz";
    }
}

Another implementation, the Nabeats Counter, will give ! Instead of telling the stupid. (!! for multiples of 3 with 3)

NabeatsuCounter.java


public class NabeatsuCounter extends AbstractCounter {

    @Override
    boolean condition1(int num) {
        return num % 3 == 0;
    }

    @Override
    boolean condition2(int num) {
        return String.valueOf(num).contains("3");
    }

    @Override
    String onCondition1(int num) {
        return String.valueOf(num) + "!";
    }

    @Override
    String onCondition2(int num) {
        return String.valueOf(num) + "!";
    }

    @Override
    String onBoth(int num) {
        return String.valueOf(num) + "!!";
    }

}

Example of use:

Usage


        System.out.println("FizzBuzz : ");
        FizzBuzzCounter counter = new FizzBuzzCounter();
        //1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz,
        counter.count(1, 20);
        System.out.println();

        System.out.println("Nabeatsu : ");
        NabeatsuCounter counter2 = new NabeatsuCounter();
        //21!,22,23!,24!,25,26,27!,28,29,30!!,31!,32!,33!!,34!,35!,36!!,37!,38!,39!!,40,
        counter2.count(21, 40);

Implementation using lambda expression

Variable processing should be passed as a function rather than overriding the hook method of the parent abstract class. In addition, for the readability of the code on the user side, the previous Builder pattern is applied, but if it is written simply, it will be passed as an argument of the template method. Also, since it is a sample code, checking whether the prerequisites of Builder are satisfied is omitted.

Counter.java


public class Counter {

    private Predicate<Integer> condition1;
    private Predicate<Integer> condition2;
    private Function<Integer, String> converter1;
    private Function<Integer, String> converter2;
    private Function<Integer, String> converterBoth;
    private int from;
    private int to;

    private Counter() {
    }

    public Counter first(Predicate<Integer> condition1, Function<Integer, String> converter1) {
        this.condition1 = condition1;
        this.converter1 = converter1;
        return this;
    }

    public Counter second(Predicate<Integer> condition2, Function<Integer, String> converter2) {
        this.condition2 = condition2;
        this.converter2 = converter2;
        return this;
    }

    public Counter onBoth(Function<Integer, String> converterBoth) {
        this.converterBoth = converterBoth;
        return this;
    }

    public Counter fromTo(int from, int to) {
        this.from = from;
        this.to = to;
        return this;
    }

    public static void count(final Consumer<Counter> consumer) {
        Counter counter = new Counter();
        consumer.accept(counter);
        counter.doCount();
    }

    private void doCount() {
        String result = IntStream.rangeClosed(from, to)
                .mapToObj(this::map)
                .collect(Collectors.joining(","));
        System.out.println(result);
    }

    private String map(int num) {
        if (condition1.test(num)) {
            return condition2.test(num) ? converterBoth.apply(num) : converter1.apply(num);
        } else {
            return condition2.test(num) ? converter2.apply(num) : String.valueOf(num);
        }
    }
}

The actual assembly and execution of FizzBuzz and Nabeats is as follows.

Usage


        //1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz
        Counter.count(c -> c.first(num -> num % 3 == 0, num -> "Fizz")
                .second(num -> num % 5 == 0, num -> "Buzz")
                .onBoth(num -> "FizzBuzz")
                .fromTo(1, 20));
        //21!,22,23!,24!,25,26,27!,28,29,30!!,31!,32!,33!!,34!,35!,36!!,37!,38!,39!!,40
        Counter.count(c -> c.first(num -> num % 3 == 0, num -> String.valueOf(num) + "!")
                .second(num -> String.valueOf(num).contains("3"), num -> String.valueOf(num) + "!")
                .onBoth(num -> String.valueOf(num) + "!!")
                .fromTo(21, 40));

Consideration

The advantage of this method is that ** there is no need to inherit a specific class to use the logic **. There is also an object-oriented idea that inheritance for the purpose of reusing implementations should be avoided. Of course, the conventional implementation method is often easier to see and manage, so I think it would be nice if it could be designed on a case-by-case basis.

Tips If you pass a hook method as a function to a template method argument, the code will be poorly visible if there are too many. In that case, it is necessary to devise such as applying the Builder pattern as in this example. Also, if the types of each argument are lined up with Predicate <T> or Function <T, R>, the responsibility of the hook method tends to be difficult to understand, so dare to define it individually as a functional interface. There is also room for consideration in ** clarifying the intention **.

Recommended Posts

Java8 Lambda expression & Stream design pattern reconsideration --Template Method pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Command pattern -
Java8 Lambda Expression & Stream Design Pattern Rethinking --Null Object Pattern -
Java8 Lambda Expression & Stream Design Pattern Rethinking --Chain of Responsibility Pattern -
Design pattern ~ Template Method ~
Java8 stream, lambda expression summary
Ruby design pattern template method pattern memo
C # chewed design pattern: Template Method
Java beginner design pattern (Factory Method pattern)
Rethinking design patterns with Java8 lambda expressions & Stream --Builder pattern -
Java design pattern
Template Method pattern
Template Method Pattern
[Java] Lambda expression
Java lambda expression
Design pattern ~ Factory Method ~
java neutral lambda expression 1
Java lambda expression variations
Java 8 lambda expression Feature
java lambda expression memo
Java lambda expression [memo]
Studying Java 8 (lambda expression)
Review java8 ~ Lambda expression ~
Java Lambda Command Pattern
Java design pattern summary
[Java] Functional interface / lambda expression
[Design pattern] Java core library
About Lambda, Stream, LocalDate of Java8
Java basic learning content 9 (lambda expression)
Comparison of thread implementation methods in Java and lambda expression description method
Now let's recap the Java lambda expression
Java method
Java method
I've reviewed Java's lambda expression, stream API, six months after I started Java.
[Java] method
[Java] method
Which is faster, method reference or lambda expression
How to use Java API with lambda expression
Java8 to start now ~ forEach and lambda expression ~