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 time, let's think about the Template Method
pattern based on the FizzBuzz problem
.
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);
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));
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