In this series, we will consider how to implement design patterns using the lambda expression / Stream API introduced in Java 8. The previous article (http://qiita.com/yonetty/items/e99b01470c3416af761c) covered the Template Method pattern.
This time we'll cover the Command pattern.
In the sample program, write the command to move the piece (Peace) that moves on the two-dimensional coordinates with the Command pattern, and try to make it a lambda expression version.
The Piece class to be operated has x-coordinate, y-coordinate, and direction (Direction) as fields, and moves back and forth (moveForward (), moveBackward ()) and sets the direction (setDirection (setDirection () Change the state by Direction)) `).
It is assumed that the initial state is the origin (0,0) and faces north (up).
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;
}
}
}
First, define the interface for Command. Have a ʻexecute (Piece)method that receives aPiece` object and processes it.
PieceCommand.java
public interface PieceCommand {
void execute(Piece piece);
}
As a concrete Command implementation, prepare a command to move back and forth by the specified number of squares (Forward, Backward) and change the direction to the left and right (TurnRight, TurnLeft).
In addition, the direction expansion to the left and right is a rotation of ± 90 degrees (π / 2 radians), and a new direction is obtained from the current direction.
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));
}
}
Here is a code example that uses these instructions to move 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());
Now let's implement the Command pattern using a lambda expression.
First, each instruction receives a Piece object and performs forward / backward movement and direction change operations, so let's realize it with the functional interfaceConsumer <T>provided by the Java 8 standard.
The PieceCommand in the previous section will be replaced with theConsumer <Piece>type.
And we will prepare a Factory to get the function that represents each instruction.
The implementation of Forward.java in the previous section dared to use the old-fashioned for syntax, but this has also been corrected to the writing method using 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));
};
}
}
Here is a code example using the lambda expression version:
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());
The advantage of the lambda expression version is that you can synthesize functions.
Specifically, the default method ʻandThen (Consumer <? Super T>)defined inConsumer
UsegeOfAndThen
Consumer<Piece> composite = PieceCommandFactory.ofForward(5)
.andThen(PieceCommandFactory.ofTurnRight())
.andThen(PieceCommandFactory.ofBackward(3))
.andThen(PieceCommandFactory.ofTurnLeft());
composite.accept(piece);
Since the synthesized result is the same Consumer <Piece> type function, it can be treated as a command (instruction) that operates Piece, similar to the one defined as a static method of PieceCommandFactory.
You can define and use new functions by compositing as follows.
Composite
//Repeat "right for right" and "turn right"
Consumer<Piece> reverse = PieceCommandFactory.ofTurnRight()
.andThen(PieceCommandFactory.ofTurnRight());
By the way, how is function composition implemented?
Looking at the source of Consumer <T>, the implementation is as follows.
Consumer.java
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
It's pretty simple. It is one line except for the precondition check.
After calling the Consumer <T> instance's own ʻaccept to which ʻandThen belongs, theConsumer <T>passed as an argument is called the ʻaccept. I think it's a good idea to simulate in your head how a lambda expression is constructed and executed when multiple ʻand Thens are concatenated.
Reduce
Instead of concatenating ʻandThenin sequence, you can implement a utility method that receives a variable array and returns the synthesizedConsumer
PieceCommandFactory#chain
@SafeVarargs
static Consumer<Piece> chain(Consumer<Piece>... commands) {
return Stream.of(commands)
.reduce((c1, c2) -> c1.andThen(c2))
.orElse(p -> {
});
}
By synthesizing the two functions sequentially with ʻand Then, they can be combined into one function as a whole. Finally, ʻor Else specifies a lambda expression that does nothing, so even if the argument commands is null, it works safely. It is okay if the length is 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());
The property of the Command pattern to proceed with processing by sequentially applying multiple Commands seems to be compatible with the concept of ** function composition ** in functional programming.
Next, I'm going to take up the Chain of Responsibility (chain of responsibility) pattern.
Recommended Posts