Wednesday, January 8, 2014

Refactor: Extract if statements to Enum + Lambdas

This post is a follow-up to my Refactor: Extract if statements to Enum post.  In this entry I make the enum implementation a bit more concise using Java 8 lambdas.

In the previous blog we refactored the "if logic" into an enum.  The FrameType enum then knew how to calculate the score for the frame with the appropriate point bonus for strikes and spares.

public enum FrameType {
STRIKE {
public int scoreFrame(int rolls[], int frameIndex) {
return MAX_PINS + rolls[frameIndex+1] + rolls[frameIndex+2];
}
public int rollsInFrame() {
return 1;
}
},
SPARE {
public int scoreFrame(int rolls[], int frameIndex) {
return MAX_PINS + rolls[frameIndex+2];
}
public int rollsInFrame() {
return 2;
}
},
OPEN {
public int scoreFrame(int rolls[], int frameIndex) {
return rolls[frameIndex] + rolls[frameIndex+1];
}
public int rollsInFrame() {
return 2;
}
};
public static FrameType classifyFrame(int firstRoll, int secondRoll) {...}
public abstract int scoreFrame(int[] rolls, int frameIndex);
public abstract int rollsInFrame();
}
view raw FrameType5.java hosted with ❤ by GitHub
Now I want to specify the logic as a functional interface. This approach expresses the same logic but in a condensed style without the ceremony overriding an abstract method. I have also refactored the rolls in frame to be a member variable - which is convenient using the enum constructor.

The scoreFrame logic is moved into a java.util.function.BiFunction instance. BiFunction<T, U, R> is a function that takes two objects, of type T and U. and returns an Object of type R. The lambda is supplied as an argument in the constructor and kept as a member of the enum instance.

The scoreFrame method changes from abstract to simply applying the function and returning the result. No changes in the BowlingGame class that uses the enum.

public enum FrameType {
STRIKE((rolls, frameIndex) -> MAX_PINS + rolls[frameIndex+1] + rolls[frameIndex+2],
1),
SPARE((rolls, frameIndex) -> MAX_PINS + rolls[frameIndex+2],
2),
OPEN((rolls, frameIndex) -> rolls[frameIndex] + rolls[frameIndex+1],
2);
private BiFunction<int[], Integer, Integer> scoreFunction;
private int rollsInFrame;
private FrameType(BiFunction<int[], Integer, Integer> scoreFunction,
int rollsInFrame) {
this.scoreFunction = scoreFunction;
this.rollsInFrame = rollsInFrame;
}
public static FrameType classifyFrame(int firstRoll, int secondRoll) {...}
public int scoreFrame(int[] rolls, int frameIndex) {
return scoreFunction.apply(rolls, frameIndex);
}
public int rollsInFrame() {
return rollsInFrame;
}
}
view raw FrameType6.java hosted with ❤ by GitHub
The result is a bit more concise, we have overcome some of the vertical space needed to express the old solution.  Does it read better?  Is it clear what the lambda does?  Let me know what you think…is it better?

No comments:

Post a Comment