Dans cette série, nous examinerons comment implémenter des modèles de conception à l'aide de l'expression lambda / API Stream introduite dans Java 8. L'article précédent (http://qiita.com/yonetty/items/e99b01470c3416af761c) couvrait le modèle Template Method
.
Cette fois, nous reprendrons le modèle Command
.
Dans l'exemple de programme, écrivez la commande pour déplacer la pièce (Peace
) qui se déplace sur les coordonnées bidimensionnelles avec le modèle Command
, et essayez d'en faire une version d'expression lambda.
La classe Piece
à utiliser a la coordonnée x, la coordonnée y et la direction ( Direction
) comme champs, et se déplace d'avant en arrière (moveForward ()
, moveBackward ()
) et définit la direction (setDirection (
setDirection () Changer l'état par Direction)) `).
On suppose que l'état initial est l'origine (0,0) et fait face au nord (vers le haut).
Piece.java
public class Piece {
private Direction direction = Direction.NORTH;
private int x = 0;
private int y = 0;
public void moveForward() {
this.x += this.direction.x();
this.y += this.direction.y();
}
public void moveBackward() {
this.x -= this.direction.x();
this.y -= this.direction.y();
}
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public static enum Direction {
NORTH(Math.PI / 2), EAST(0), SOUTH(Math.PI / -2), WEST(Math.PI);
final private double radian;
final private int x;
final private int y;
Direction(double radian) {
this.radian = radian;
this.x = (int) Math.cos(radian);
this.y = (int) Math.sin(radian);
}
public static Direction valueOf(double radian) {
return Stream.of(Direction.values())
.filter(d -> d.x == (int) Math.cos(radian) && d.y == (int) Math.sin(radian))
.findAny()
.orElseThrow(IllegalArgumentException::new);
}
public int x() {
return this.x;
}
public int y() {
return this.y;
}
public double radian() {
return this.radian;
}
}
}
Tout d'abord, définissez l'interface pour Command
. Avoir une méthode ʻexecute (Piece) qui reçoit et traite l'objet
Piece`.
PieceCommand.java
public interface PieceCommand {
void execute(Piece piece);
}
En tant qu'implémentation concrète de Commande
, préparez une commande pour aller et venir du nombre de carrés spécifié ( Avant
, Arrière
) et un changement de direction vers la gauche et la droite ( TurnRight
, TurnLeft
).
De plus, l'expansion de direction vers la gauche et la droite est une rotation de ± 90 degrés («π / 2» radian), et une nouvelle direction est obtenue à partir de la direction du courant.
Forward.java
public class Forward implements PieceCommand {
private final int step;
public Forward(int step) {
this.step = step;
}
@Override
public void execute(Piece piece) {
for (int i = 0; i < step; i++) {
piece.moveForward();
}
}
}
TurnLeft.java
public class TurnLeft implements PieceCommand {
@Override
public void execute(Piece piece) {
double radian = piece.getDirection().radian() + Math.PI / 2;
piece.setDirection(Piece.Direction.valueOf(radian));
}
}
Voici un exemple de code qui utilise ces instructions pour déplacer le Piece
:
Usage
PieceCommand cmd1 = new Forward(5);
cmd1.execute(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
PieceCommand cmd2 = new TurnRight();
cmd2.execute(piece);
assertEquals(Piece.Direction.EAST, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
PieceCommand cmd3 = new Backward(3);
cmd3.execute(piece);
assertEquals(Piece.Direction.EAST, piece.getDirection());
assertEquals(-3, piece.getX());
assertEquals(5, piece.getY());
PieceCommand cmd4 = new TurnLeft();
cmd4.execute(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(-3, piece.getX());
assertEquals(5, piece.getY());
Maintenant, implémentons le modèle Command
en utilisant une expression lambda.
Tout d'abord, chaque instruction reçoit un objet Piece
et effectue des opérations de mouvement avant / arrière et de changement de direction, alors réalisons-la avec l'interface fonctionnelleConsumer <T>
fournie par le standard Java 8.
La PieceCommand
dans la section précédente sera remplacée par le typeConsumer <Piece>
.
Et nous allons préparer une Factory
pour obtenir la fonction qui représente chaque instruction.
L'implémentation de Forward.java
dans la section précédente a osé utiliser la syntaxe à l'ancienne, mais cela a également été corrigé par la méthode d'écriture utilisant Stream API
.
PieceCommandFactory.java
public class PieceCommandFactory {
static Consumer<Piece> ofForward(final int step) {
return p -> {
IntStream.range(0, step)
.forEach(i -> p.moveForward());
};
}
static Consumer<Piece> ofBackward(final int step) {
return p -> {
IntStream.range(0, step)
.forEach(i -> p.moveBackward());
};
}
static Consumer<Piece> ofTurnRight() {
return p -> {
double radian = p.getDirection().radian() - Math.PI / 2;
p.setDirection(Piece.Direction.valueOf(radian));
};
}
static Consumer<Piece> ofTurnLeft() {
return p -> {
double radian = p.getDirection().radian() + Math.PI / 2;
p.setDirection(Piece.Direction.valueOf(radian));
};
}
}
Voici un exemple de code utilisant la version de l'expression lambda:
Usage
Consumer<Piece> cmd1 = PieceCommandFactory.ofForward(5);
cmd1.accept(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
Consumer<Piece> cmd2 = PieceCommandFactory.ofTurnRight();
cmd2.accept(piece);
assertEquals(Piece.Direction.EAST, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
Consumer<Piece> cmd3 = PieceCommandFactory.ofBackward(3);
cmd3.accept(piece);
assertEquals(Piece.Direction.EAST, piece.getDirection());
assertEquals(-3, piece.getX());
assertEquals(5, piece.getY());
Consumer<Piece> cmd4 = PieceCommandFactory.ofTurnLeft();
cmd4.accept(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(-3, piece.getX());
assertEquals(5, piece.getY());
L'avantage de la version d'expression lambda est que vous pouvez synthétiser des fonctions.
Plus précisément, la méthode par défaut ʻandThen (Consumer <? Super T>) définie dans
Consumer
UsegeOfAndThen
Consumer<Piece> composite = PieceCommandFactory.ofForward(5)
.andThen(PieceCommandFactory.ofTurnRight())
.andThen(PieceCommandFactory.ofBackward(3))
.andThen(PieceCommandFactory.ofTurnLeft());
composite.accept(piece);
Puisque le résultat synthétisé est la même fonction de type «Consumer
Composite
//Répétez "droite pour droite" et "tourner à droite"
Consumer<Piece> reverse = PieceCommandFactory.ofTurnRight()
.andThen(PieceCommandFactory.ofTurnRight());
Au fait, comment la composition des fonctions est-elle implémentée?
En regardant la source de Consumer <T>
, il a l'implémentation suivante.
Consumer.java
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
C'est assez simple. Il s'agit d'une ligne sauf pour le contrôle des conditions préalables.
Après avoir appelé le propre ʻacceptde l'instance
Consumer auquel appartient ʻandThen
, leConsumer <T>
passé comme argument est appelé ʻaccept. Je pense que c'est une bonne idée de simuler dans votre esprit comment l'expression lambda est construite et exécutée lorsque plusieurs ʻet Then
s sont concaténés.
Reduce
Au lieu de concaténer «et ensuite» en séquence, vous pouvez implémenter une méthode utilitaire qui reçoit un tableau de variables et renvoie le «Consumer
PieceCommandFactory#chain
@SafeVarargs
static Consumer<Piece> chain(Consumer<Piece>... commands) {
return Stream.of(commands)
.reduce((c1, c2) -> c1.andThen(c2))
.orElse(p -> {
});
}
En synthétisant les deux fonctions séquentiellement avec «et Alors», elles peuvent être combinées en une seule fonction dans son ensemble.
Enfin, ʻou Elsespécifie une expression lambda qui ne fait rien, donc même si l'argument
commandes est
null`, cela fonctionne en toute sécurité. Ce n'est pas grave si la longueur est de 1.
SafeChain
Consumer<Piece> composite = PieceCommandFactory.chain(PieceCommandFactory.ofForward(5));
composite.accept(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
Consumer<Piece> empty = PieceCommandFactory.chain();
empty.accept(piece);
assertEquals(Piece.Direction.NORTH, piece.getDirection());
assertEquals(0, piece.getX());
assertEquals(5, piece.getY());
La propriété du modèle «Command» de procéder en appliquant plusieurs commandes séquentiellement semble être compatible avec le concept de ** composition de fonction ** en programmation fonctionnelle. Ensuite, je vais reprendre le modèle de «Chaîne de responsabilité» («chaîne de responsabilité»).
Recommended Posts