Created
November 26, 2020 08:09
-
-
Save h1ddengames/02168cb949ea2797d920782df66601b4 to your computer and use it in GitHub Desktop.
Java Command Pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.hiddengames.command; | |
@FunctionalInterface | |
public interface ICommand<T> { | |
void execute(T item); | |
//void undo(T item); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.hiddengames.command; | |
@FunctionalInterface | |
public interface IContinue<T> { | |
T then(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.hiddengames.command; | |
public class Light { | |
public enum Color { WHITE, RED, BLUE, GREEN } | |
private boolean on = false; | |
private xyz.hiddengames.remote.Light.Color color = xyz.hiddengames.remote.Light.Color.WHITE; | |
public void switchOn() { on = true; } | |
public void switchOff() { on = false; } | |
public void setColor(xyz.hiddengames.remote.Light.Color color) { | |
if(on) { | |
throw new RuntimeException("Cannot change colors while light is on."); | |
} else { this.color = color; } | |
} | |
public boolean isOn() { return on; } | |
public xyz.hiddengames.remote.Light.Color getColor() { return color; } | |
@Override public String toString() { return getStatus(); } | |
private String getStatus() { | |
String status = "The light is currently: " + getColor() + " and is "; | |
if(isOn()) { status += "ON."; } else { status += "OFF."; } | |
return status; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.hiddengames.command; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class RemoteControl<T> implements IContinue<RemoteControl> { | |
// The current command to execute. | |
private ICommand<T> command; | |
// What the remote control is controlling. | |
T item; | |
// Starts at -1 to simulate array index starting at 0. | |
// Example: when 1 command is in the list (listOfCommands.size() == 1) | |
// this index variable will be 0 which will allow the use of the following code: listOfCommands.get(currentCommandIndex); | |
int currentCommandIndex = -1; | |
List<ICommand<T>> listOfCommands = new ArrayList<>(); | |
public RemoteControl(T item) { setItem(item); } | |
public IContinue<RemoteControl> setItem(T item) { | |
this.item = item; | |
return this; | |
} | |
public IContinue<RemoteControl> setCommand(ICommand<T> command) { | |
this.command = command; | |
return this; | |
} | |
public IContinue<RemoteControl> pressButton() { | |
listOfCommands.add(command); | |
currentCommandIndex++; | |
command.execute(item); | |
return this; | |
} | |
public IContinue<RemoteControl> undo() { | |
if(currentCommandIndex == 0) { | |
throw new RuntimeException("There is nothing to undo."); | |
} | |
currentCommandIndex--; | |
command = listOfCommands.get(currentCommandIndex); | |
command.execute(item); | |
return this; | |
} | |
public IContinue<RemoteControl> redo() { | |
if(currentCommandIndex >= listOfCommands.size() - 1) { | |
throw new RuntimeException("There is nothing to redo."); | |
} | |
currentCommandIndex++; | |
command = listOfCommands.get(currentCommandIndex); | |
command.execute(item); | |
return this; | |
} | |
// Allows the use of method chaining: | |
// RemoteControl<Light> lightController = new RemoteControl<>(); | |
// lightController.setCommand(new Commands.LightOnCommand).then().pressButton().then().undo().then().redo(); | |
// It is possible to change all methods' return type in this class to RemoteControl<T> in order to change the above method | |
// chaining to the following: | |
// RemoteControl<Light> lightController = new RemoteControl<>(); | |
// lightController.setCommand(new Commands.LightOnCommand).pressButton().undo().redo(); | |
public RemoteControl<T> then() { return this; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Order; | |
import org.junit.jupiter.api.Test; | |
import xyz.hiddengames.command.Commands; | |
import xyz.hiddengames.command.ICommand; | |
import xyz.hiddengames.command.Light; | |
import xyz.hiddengames.command.RemoteControl; | |
import static org.junit.jupiter.api.Assertions.*; | |
public class RemoteControlTest { | |
Light porchLight = new Light(); | |
RemoteControl<Light> porchLightController = new RemoteControl<>(porchLight); | |
ICommand<Light> lightOnCommand = new Commands.LightOnCommand(); | |
ICommand<Light> lightOffCommand = new Commands.LightOffCommand(); | |
@Test | |
@Order(1) | |
void defaultStateOffTest() { | |
assertFalse(porchLight.isOn()); | |
} | |
@Test | |
@Order(2) | |
void defaultStateWhiteColorTest() { | |
assertEquals(Light.Color.WHITE, porchLight.getColor()); | |
} | |
@Test | |
@Order(3) | |
void defaultOutputTest() { | |
assertEquals("The light is currently: WHITE and is OFF.", porchLight.toString()); | |
} | |
@Test | |
void turnOnTest() { | |
porchLightController.setCommand(lightOnCommand); | |
porchLightController.pressButton(); | |
assertTrue(porchLight.isOn()); | |
} | |
@Test | |
void turnOffTest() { | |
porchLight.switchOn(); | |
porchLightController.setCommand(lightOffCommand); | |
porchLightController.pressButton(); | |
assertFalse(porchLight.isOn()); | |
} | |
@Test | |
void integrationTurnOnThenOffTest() { | |
porchLightController.setCommand(lightOnCommand); | |
porchLightController.pressButton(); | |
assertTrue(porchLight.isOn()); | |
porchLightController.setCommand(lightOffCommand); | |
porchLightController.pressButton(); | |
assertFalse(porchLight.isOn()); | |
} | |
@Test | |
void undoTest() { | |
porchLightController.setCommand(lightOnCommand); | |
porchLightController.pressButton(); | |
assertTrue(porchLight.isOn()); | |
porchLightController.undo(); | |
assertFalse(porchLight.isOn()); | |
} | |
@Test | |
void undoThenRedoTest() { | |
porchLightController.setCommand(lightOnCommand); | |
porchLightController.pressButton(); | |
assertTrue(porchLight.isOn()); | |
porchLightController.undo(); | |
assertFalse(porchLight.isOn()); | |
porchLightController.redo(); | |
assertTrue(porchLight.isOn()); | |
} | |
@Test | |
void remoteLevelUndoAndRedoTest() { | |
porchLightController.setCommand(lightOnCommand); | |
porchLightController.pressButton(); | |
assertTrue(porchLight.isOn()); | |
porchLightController.setCommand(lightOffCommand); | |
porchLightController.pressButton(); | |
assertFalse(porchLight.isOn()); | |
porchLightController.undo(); | |
assertTrue(porchLight.isOn()); | |
porchLightController.redo(); | |
assertFalse(porchLight.isOn()); | |
Throwable exceptionThatWasThrown = assertThrows(RuntimeException.class, () -> { | |
porchLightController.redo(); | |
}); | |
assertEquals(exceptionThatWasThrown.getMessage(), "There is nothing to redo."); | |
porchLightController.undo(); | |
assertTrue(porchLight.isOn()); | |
exceptionThatWasThrown = assertThrows(RuntimeException.class, () -> { | |
porchLightController.undo(); | |
}); | |
assertEquals(exceptionThatWasThrown.getMessage(), "There is nothing to undo."); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment