Skip to content

Instantly share code, notes, and snippets.

@h1ddengames
Created November 26, 2020 08:09
Show Gist options
  • Save h1ddengames/02168cb949ea2797d920782df66601b4 to your computer and use it in GitHub Desktop.
Save h1ddengames/02168cb949ea2797d920782df66601b4 to your computer and use it in GitHub Desktop.
Java Command Pattern
package xyz.hiddengames.command;
@FunctionalInterface
public interface ICommand<T> {
void execute(T item);
//void undo(T item);
}
package xyz.hiddengames.command;
@FunctionalInterface
public interface IContinue<T> {
T then();
}
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;
}
}
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; }
}
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