Skip to content

Commit e0b412b

Browse files
committed
The Command Pattern
1 parent 3bb43ce commit e0b412b

File tree

13 files changed

+496
-0
lines changed

13 files changed

+496
-0
lines changed

src/main/java/command/App.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package command;
2+
3+
import command.service.command.Command;
4+
import command.service.command.impl.DelayedCommand;
5+
import command.service.command.impl.LightOffCommand;
6+
import command.service.command.impl.LightOnCommand;
7+
import command.service.command.impl.MacroCommand;
8+
import command.service.device.SmartDeviceController;
9+
import command.service.device.impl.LightDevice;
10+
11+
import java.util.Arrays;
12+
import java.util.concurrent.TimeUnit;
13+
14+
public class App {
15+
public static void main(String[] args) throws InterruptedException {
16+
// Create smart device controller
17+
SmartDeviceController controller = new SmartDeviceController();
18+
19+
// Create device
20+
LightDevice livingRoomLight = new LightDevice("livingRoomLight");
21+
LightDevice bedroomLight = new LightDevice("bedroomLight");
22+
23+
// Register device
24+
controller.registerDevice(livingRoomLight);
25+
controller.registerDevice(bedroomLight);
26+
27+
// Create command
28+
Command livingRoomOn = new LightOnCommand(livingRoomLight);
29+
Command livingRoomOff = new LightOffCommand(livingRoomLight);
30+
Command bedroomOn = new LightOnCommand(bedroomLight);
31+
Command bedroomOff = new LightOffCommand(bedroomLight);
32+
33+
// Create macro command
34+
Command homeArrival = new MacroCommand("Home Mode", Arrays.asList(
35+
livingRoomOn
36+
));
37+
38+
Command bedtime = new MacroCommand("Sleep Mode", Arrays.asList(
39+
livingRoomOff,
40+
bedroomOn
41+
));
42+
43+
// Create delay command
44+
Command delayedLightOff = new DelayedCommand(bedroomOff, 5000);
45+
46+
// Assign commands to slots of controller
47+
controller.assignCommand("A1", livingRoomOn);
48+
controller.assignCommand("A2", livingRoomOff);
49+
controller.assignCommand("B1", bedroomOn);
50+
controller.assignCommand("B2", bedroomOff);
51+
controller.assignCommand("C1", homeArrival);
52+
controller.assignCommand("C2", bedtime);
53+
controller.assignCommand("D1", delayedLightOff);
54+
55+
// Execute command
56+
System.out.println("===== Execute Home Mode =====");
57+
controller.pressButton("C1");
58+
59+
TimeUnit.SECONDS.sleep(1);
60+
System.out.println("\n===== Current Status =====");
61+
System.out.println(controller.getStatusReport());
62+
63+
TimeUnit.SECONDS.sleep(1);
64+
System.out.println("===== Execute Sleep Mode =====");
65+
controller.pressButton("C2");
66+
67+
TimeUnit.SECONDS.sleep(1);
68+
System.out.println("\n===== Current Status =====");
69+
System.out.println(controller.getStatusReport());
70+
71+
System.out.println("===== Delay By 5 Seconds To Turn Off The Bedroom Light =====");
72+
controller.pressButton("D1");
73+
74+
System.out.println("\n===== Execute Undo Operation =====");
75+
controller.undo(); // Cancel delay command
76+
controller.undo(); // Cancel sleep mode
77+
78+
System.out.println("\n===== Execute Redo Operation =====");
79+
controller.redo(); // Redo sleep mode
80+
81+
TimeUnit.SECONDS.sleep(6); // Waiting for delayed command execution
82+
83+
System.out.println("\n===== Final Status =====");
84+
System.out.println(controller.getStatusReport());
85+
86+
System.out.println("\n===== Command History =====");
87+
System.out.println(controller.getCommandHistory());
88+
89+
controller.shutdown();
90+
}
91+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package command.listener;
2+
3+
import command.service.command.Command;
4+
5+
public interface CommandListener {
6+
void onCommandAction(Command command, boolean executed);
7+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package command.manager;
2+
3+
import com.google.common.collect.Lists;
4+
import command.listener.CommandListener;
5+
import command.service.command.Command;
6+
import command.service.command.impl.RedoCommand;
7+
import command.service.command.impl.UndoCommand;
8+
9+
import java.util.ArrayDeque;
10+
import java.util.Deque;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
public class CommandManager {
15+
private final Deque<Command> history = new ArrayDeque<>();
16+
private final Deque<Command> redoStack = new ArrayDeque<>();
17+
private final List<Command> log = Lists.newArrayList();
18+
private final List<CommandListener> listeners = Lists.newArrayList();
19+
20+
public void execute(Command command) {
21+
command.execute();
22+
history.push(command);
23+
redoStack.clear(); // Clear the redo stack when executing a new command
24+
log.add(command);
25+
notifyListeners(command, true);
26+
}
27+
28+
public void undo() {
29+
if (!history.isEmpty()) {
30+
Command command = history.pop();
31+
command.undo();
32+
redoStack.push(command);
33+
log.add(new UndoCommand(command));
34+
notifyListeners(command, false);
35+
}
36+
}
37+
38+
public void redo() {
39+
if (!redoStack.isEmpty()) {
40+
Command command = redoStack.pop();
41+
command.execute();
42+
history.push(command);
43+
log.add(new RedoCommand(command));
44+
notifyListeners(command, true);
45+
}
46+
}
47+
48+
public void addListener(CommandListener listener) {
49+
listeners.add(listener);
50+
}
51+
52+
public String getLog() {
53+
return log.stream()
54+
.map(Command::description)
55+
.collect(Collectors.joining("\n"));
56+
}
57+
58+
private void notifyListeners(Command command, boolean executed) {
59+
listeners.forEach(l -> l.onCommandAction(command, executed));
60+
}
61+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package command.service.command;
2+
3+
public interface Command {
4+
void execute();
5+
6+
void undo();
7+
8+
String description();
9+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package command.service.command.impl;
2+
3+
import command.service.command.Command;
4+
5+
import java.util.concurrent.Executors;
6+
import java.util.concurrent.ScheduledExecutorService;
7+
import java.util.concurrent.ScheduledFuture;
8+
import java.util.concurrent.TimeUnit;
9+
10+
public class DelayedCommand implements Command {
11+
private final Command command;
12+
private final long delayMillis;
13+
private ScheduledFuture<?> future;
14+
private final ScheduledExecutorService scheduler;
15+
16+
public DelayedCommand(Command command, long delayMillis) {
17+
this.command = command;
18+
this.delayMillis = delayMillis;
19+
this.scheduler = Executors.newSingleThreadScheduledExecutor();
20+
}
21+
22+
@Override
23+
public void execute() {
24+
future = scheduler.schedule(() -> {
25+
System.out.println("[DELAY EXECUTE] " + command.description());
26+
command.execute();
27+
}, delayMillis, TimeUnit.MILLISECONDS);
28+
}
29+
30+
@Override
31+
public void undo() {
32+
if (future != null && !future.isDone()) {
33+
future.cancel(true);
34+
System.out.println("[CANCEL DELAY EXECUTE COMMAND] " + command.description());
35+
} else {
36+
command.undo();
37+
}
38+
scheduler.shutdown();
39+
}
40+
41+
@Override
42+
public String description() {
43+
return String.format("Delay execute after %.1fseconds: %s", delayMillis / 1000.0, command.description());
44+
}
45+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package command.service.command.impl;
2+
3+
import command.service.command.Command;
4+
import command.service.device.impl.LightDevice;
5+
6+
public class LightOffCommand implements Command {
7+
private final LightDevice light;
8+
private int previousBrightness;
9+
private boolean wasOn;
10+
11+
public LightOffCommand(LightDevice light) {
12+
this.light = light;
13+
}
14+
15+
@Override
16+
public void execute() {
17+
wasOn = light.isOn();
18+
previousBrightness = light.getBrightness();
19+
light.turnOff();
20+
}
21+
22+
@Override
23+
public void undo() {
24+
if (wasOn) {
25+
light.turnOn();
26+
light.setBrightness(previousBrightness);
27+
}
28+
}
29+
30+
@Override
31+
public String description() {
32+
return "turn off " + light.getName();
33+
}
34+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package command.service.command.impl;
2+
3+
import command.service.command.Command;
4+
import command.service.device.impl.LightDevice;
5+
6+
public class LightOnCommand implements Command {
7+
private final LightDevice light;
8+
private int previousBrightness;
9+
10+
public LightOnCommand(LightDevice light) {
11+
this.light = light;
12+
}
13+
14+
@Override
15+
public void execute() {
16+
previousBrightness = light.getBrightness();
17+
light.turnOn();
18+
light.setBrightness(100);
19+
}
20+
21+
@Override
22+
public void undo() {
23+
light.turnOff();
24+
light.setBrightness(previousBrightness);
25+
}
26+
27+
@Override
28+
public String description() {
29+
return "turn on " + light.getName() + " and set brightness to 100%";
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package command.service.command.impl;
2+
3+
import com.google.common.collect.Lists;
4+
import command.service.command.Command;
5+
6+
import java.util.List;
7+
import java.util.ListIterator;
8+
9+
public class MacroCommand implements Command {
10+
private final String name;
11+
private final List<Command> commands;
12+
13+
public MacroCommand(String name, List<Command> commands) {
14+
this.name = name;
15+
this.commands = Lists.newArrayList(commands);
16+
}
17+
18+
@Override
19+
public void execute() {
20+
System.out.println("===== Execution Scene: " + name + " =====");
21+
commands.forEach(Command::execute);
22+
}
23+
24+
@Override
25+
public void undo() {
26+
System.out.println("===== Cancel Scene: " + name + " =====");
27+
// Reverse execution revocation
28+
ListIterator<Command> iterator = commands.listIterator(commands.size());
29+
while (iterator.hasPrevious()) {
30+
iterator.previous().undo();
31+
}
32+
}
33+
34+
@Override
35+
public String description() {
36+
return "Scene Mode: " + name + " (" + commands.size() + " Commands)";
37+
}
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package command.service.command.impl;
2+
3+
import command.service.command.Command;
4+
5+
public class RedoCommand implements Command {
6+
private final Command command;
7+
8+
public RedoCommand(Command command) {
9+
this.command = command;
10+
}
11+
12+
@Override
13+
public void execute() {
14+
15+
}
16+
17+
@Override
18+
public void undo() {
19+
20+
}
21+
22+
@Override
23+
public String description() {
24+
return "[REDO]" + command.description();
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package command.service.command.impl;
2+
3+
import command.service.command.Command;
4+
5+
public class UndoCommand implements Command {
6+
private final Command command;
7+
8+
public UndoCommand(Command command) {
9+
this.command = command;
10+
}
11+
12+
@Override
13+
public void execute() {
14+
15+
}
16+
17+
@Override
18+
public void undo() {
19+
20+
}
21+
22+
@Override
23+
public String description() {
24+
return "[UNDO]" + command.description();
25+
}
26+
}

0 commit comments

Comments
 (0)