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 a
Piece` 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 in
Consumer
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 Then
s are concatenated.
Reduce
Instead of concatenating ʻandThenin sequence, you can implement a utility method that receives a variable array and returns the synthesized
Consumer
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 Command
s 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