In dieser Reihe werden wir uns überlegen, wie Entwurfsmuster mithilfe der in Java 8 eingeführten Lambda-Ausdrucks- / Stream-API implementiert werden. Der vorherige Artikel (http://qiita.com/yonetty/items/e99b01470c3416af761c) befasste sich mit dem Muster "Vorlagenmethode".
Dieses Mal werden wir das "Befehl" -Muster aufgreifen. Schreiben Sie im Beispielprogramm den Befehl zum Verschieben des Stücks ("Frieden"), das sich auf den zweidimensionalen Koordinaten mit dem "Befehl" -Muster bewegt, und versuchen Sie, es zu einer Lambda-Ausdrucksversion zu machen.
Die zu bedienende "Piece" -Klasse hat x-Koordinate, y-Koordinate und Richtung ("Richtung") als Felder und bewegt sich hin und her ("moveForward ()", "moveBackward ()") und legt die Richtung ("setDirection" ("setDirection") fest. Ändern Sie den Status nach Richtung)) `). Es wird angenommen, dass der Ausgangszustand der Ursprung (0,0) ist und nach Norden (nach oben) zeigt.
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;
}
}
}
Definieren Sie zunächst die Schnittstelle für "Befehl". Es hat eine "execute (Piece)" - Methode, die ein "Piece" -Objekt empfängt und verarbeitet.
PieceCommand.java
public interface PieceCommand {
void execute(Piece piece);
}
Bereiten Sie als konkrete Command
-Implementierung einen Befehl vor, um die angegebene Anzahl von Quadraten ( Forward
, Backward
) und eine Richtungsänderung nach links und rechts ( TurnRight
, TurnLeft
) hin und her zu bewegen.
Zusätzlich ist die Richtungserweiterung nach links und rechts eine Drehung von ± 90 Grad ("π / 2" Radian), und aus der aktuellen Richtung wird eine neue Richtung erhalten.
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));
}
}
Hier ist ein Codebeispiel, das diese Anweisungen verwendet, um Piece
zu verschieben:
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());
Lassen Sie uns nun das Befehlsmuster mit einem Lambda-Ausdruck implementieren.
Zunächst empfängt jeder Befehl ein "Piece" -Objekt und führt Vorwärts- / Rückwärtsbewegungs- und Richtungsänderungsoperationen durch. Lassen Sie uns dies also mit der vom Java 8-Standard bereitgestellten Funktionsschnittstelle "Consumer PieceCommand
im vorherigen Abschnitt wird durch den TypConsumer <Piece>
ersetzt.
Und wir werden eine "Fabrik" vorbereiten, um die Funktion zu erhalten, die jede Anweisung darstellt.
Die Implementierung von "Forward.java" im vorherigen Abschnitt wagte es, die altmodische Syntax zu verwenden, dies wurde jedoch auch mit "Stream API" auf die Schreibmethode korrigiert.
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));
};
}
}
Hier ist ein Codebeispiel mit der Lambda-Ausdrucksversion:
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());
Der Vorteil der Lambda-Ausdrucksversion besteht darin, dass Sie Funktionen synthetisieren können.
Insbesondere wird die in "Consumer
UsegeOfAndThen
Consumer<Piece> composite = PieceCommandFactory.ofForward(5)
.andThen(PieceCommandFactory.ofTurnRight())
.andThen(PieceCommandFactory.ofBackward(3))
.andThen(PieceCommandFactory.ofTurnLeft());
composite.accept(piece);
Da das synthetisierte Ergebnis dieselbe Funktion vom Typ "Consumer
Composite
//Wiederholen Sie "rechts für rechts" und "rechts abbiegen"
Consumer<Piece> reverse = PieceCommandFactory.ofTurnRight()
.andThen(PieceCommandFactory.ofTurnRight());
Wie wird die Funktionszusammensetzung übrigens implementiert?
Wenn man sich die Quelle von "Consumer
Consumer.java
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
Es ist ziemlich einfach. Es ist eine Zeile mit Ausnahme der Vorbedingungsprüfung.
Nach dem Aufruf des "accept" der "Consumer andThen
s verkettet werden.
Reduce
Anstatt "andThen" nacheinander zu verketten, können Sie eine Dienstprogrammmethode implementieren, die ein variables Array empfängt und den synthetisierten "Consumer
PieceCommandFactory#chain
@SafeVarargs
static Consumer<Piece> chain(Consumer<Piece>... commands) {
return Stream.of(commands)
.reduce((c1, c2) -> c1.andThen(c2))
.orElse(p -> {
});
}
Durch sequentielles Synthetisieren der beiden Funktionen mit "andThen" können sie zu einer Funktion als Ganzes kombiniert werden. Da "orElse" einen Lambda-Ausdruck angibt, der nichts bewirkt, funktioniert er schließlich auch dann sicher, wenn das Argument "Befehle" "Null" ist. Es ist in Ordnung, wenn die Länge 1 ist.
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());
Die Eigenschaft des Befehlsmusters, mit der Verarbeitung fortzufahren, indem mehrere Befehle nacheinander angewendet werden, scheint mit dem Konzept der ** Funktionszusammensetzung ** in der Funktionsprogrammierung kompatibel zu sein. Als nächstes werde ich das Muster der "Kette der Verantwortung" ("Kette der Verantwortung") aufgreifen.