Nous examinerons une nouvelle méthode d'implémentation des modèles de conception à l'aide de l'API Stream, qui est une expression lambda introduite dans Java8.
Dans l'article précédent (http://qiita.com/yonetty/items/a6afb566c4079a6cf34c), nous avons examiné comment implémenter le modèle Builder
à l'aide d'expressions lambda.
Cette fois, pensons au modèle Template Method
basé sur le problème FizzBuzz
.
Afin de réutiliser un comportement commun (logique), définissez une méthode modèle dans la classe abstraite avec final
, et définissez la partie variable comme une méthode hook ʻabstract. En implémentant la méthode hook dans la classe concrète, il est possible d'exprimer un comportement individuel tout en conservant un comportement commun. (La définition d'une méthode de modèle
final` empêche les sous-classes de remplacer le comportement commun)
La classe abstraite ressemble à ceci:
count (int, int)
est la méthode du modèle.
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);
}
Une implémentation de FizzBuzz. Remplace 5 méthodes «abstraites».
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";
}
}
Une autre implémentation, le Nabeats Counter, donnera un «!» Au lieu de dire au stupide. (!!
pour les multiples de 3 avec 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) + "!!";
}
}
Exemple d'utilisation:
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);
Le traitement des variables doit être passé en tant que fonction plutôt que de remplacer la méthode hook de la classe abstraite parente.
Pour la lisibilité du code côté utilisateur, le modèle précédent Builder
est appliqué, mais s'il est écrit simplement, il sera passé comme argument de la méthode modèle.
De plus, puisqu'il s'agit d'un exemple de code, la vérification de la satisfaction des conditions préalables de «Builder» est omise.
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);
}
}
}
L'assemblage et l'exécution réels de FizzBuzz et Nabeats sont les suivants.
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));
L'avantage de cette méthode est que ** vous n'avez pas à hériter d'une classe spécifique pour utiliser la logique **. Il existe également une idée orientée objet selon laquelle l'héritage dans le but de réutiliser l'implémentation doit être évité. Bien sûr, il existe de nombreux cas où la méthode de mise en œuvre conventionnelle a une meilleure visibilité et est plus facile à gérer, donc je pense qu'il serait bon qu'elle puisse être conçue au cas par cas.
Tips
Si vous passez la méthode hook en tant que fonction à l'argument de la méthode template, le code sera mal visible s'il y en a trop. Dans ce cas, il est nécessaire de concevoir comme appliquer le modèle «Builder» comme dans cet exemple.
Aussi, si les types de chaque argument sont alignés avec Predicate <T>
ou Function <T, R>
, la responsabilité de la méthode hook a tendance à être difficile à comprendre, alors osez la définir individuellement comme une interface de type de fonction. Il est également possible d'envisager ** de clarifier l'intention **.
Recommended Posts