Merge pull request #1531 from ravening/command-functional
Refactor the command pattern to use lambda functions
This commit is contained in:
		| @@ -48,18 +48,16 @@ public class Wizard { | ||||
|  | ||||
|   public Wizard() {} | ||||
|  | ||||
|   public void castSpell(Command command, Target target) { | ||||
|     LOGGER.info("{} casts {} at {}", this, command, target); | ||||
|     command.execute(target); | ||||
|     undoStack.offerLast(command); | ||||
|   public void castSpell(Runnable runnable) { | ||||
|     runnable.run(); | ||||
|     undoStack.offerLast(runnable); | ||||
|   } | ||||
|  | ||||
|   public void undoLastSpell() { | ||||
|     if (!undoStack.isEmpty()) { | ||||
|       var previousSpell = undoStack.pollLast(); | ||||
|       redoStack.offerLast(previousSpell); | ||||
|       LOGGER.info("{} undoes {}", this, previousSpell); | ||||
|       previousSpell.undo(); | ||||
|       previousSpell.run(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -67,8 +65,7 @@ public class Wizard { | ||||
|     if (!redoStack.isEmpty()) { | ||||
|       var previousSpell = redoStack.pollLast(); | ||||
|       undoStack.offerLast(previousSpell); | ||||
|       LOGGER.info("{} redoes {}", this, previousSpell); | ||||
|       previousSpell.redo(); | ||||
|       previousSpell.run(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -79,84 +76,7 @@ public class Wizard { | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Next we present the spell hierarchy. | ||||
|  | ||||
| ```java | ||||
| public interface Command { | ||||
|  | ||||
|   void execute(Target target); | ||||
|  | ||||
|   void undo(); | ||||
|  | ||||
|   void redo(); | ||||
|  | ||||
|   String toString(); | ||||
| } | ||||
|  | ||||
| public class InvisibilitySpell implements Command { | ||||
|  | ||||
|   private Target target; | ||||
|  | ||||
|   @Override | ||||
|   public void execute(Target target) { | ||||
|     target.setVisibility(Visibility.INVISIBLE); | ||||
|     this.target = target; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void undo() { | ||||
|     if (target != null) { | ||||
|       target.setVisibility(Visibility.VISIBLE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void redo() { | ||||
|     if (target != null) { | ||||
|       target.setVisibility(Visibility.INVISIBLE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Invisibility spell"; | ||||
|   } | ||||
| } | ||||
|  | ||||
| public class ShrinkSpell implements Command { | ||||
|  | ||||
|   private Size oldSize; | ||||
|   private Target target; | ||||
|  | ||||
|   @Override | ||||
|   public void execute(Target target) { | ||||
|     oldSize = target.getSize(); | ||||
|     target.setSize(Size.SMALL); | ||||
|     this.target = target; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void undo() { | ||||
|     if (oldSize != null && target != null) { | ||||
|       var temp = target.getSize(); | ||||
|       target.setSize(oldSize); | ||||
|       oldSize = temp; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void redo() { | ||||
|     undo(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Shrink spell"; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Finally, we have the goblin who's the target of the spells. | ||||
| Next, we have the goblin who's the target of the spells. | ||||
|  | ||||
| ```java | ||||
| public abstract class Target { | ||||
| @@ -203,33 +123,73 @@ public class Goblin extends Target { | ||||
|     return "Goblin"; | ||||
|   } | ||||
|  | ||||
|   public void changeSize() { | ||||
|     var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; | ||||
|     setSize(oldSize); | ||||
|   } | ||||
|  | ||||
|   public void changeVisibility() { | ||||
|     var visible = getVisibility() == Visibility.INVISIBLE | ||||
|           ? Visibility.VISIBLE : Visibility.INVISIBLE; | ||||
|     setVisibility(visible); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Finally we have the wizard in main function who casts spell | ||||
|  | ||||
| ```java | ||||
| public static void main(String[] args) { | ||||
|   var wizard = new Wizard(); | ||||
|   var goblin = new Goblin(); | ||||
|  | ||||
|   // casts shrink/unshrink spell | ||||
|   wizard.castSpell(goblin::changeSize); | ||||
|  | ||||
|   // casts visible/invisible spell | ||||
|   wizard.castSpell(goblin::changeVisibility); | ||||
|  | ||||
|   // undo and redo casts | ||||
|    wizard.undoLastSpell(); | ||||
|    wizard.redoLastSpell(); | ||||
| ``` | ||||
|  | ||||
| Here's the whole example in action. | ||||
|  | ||||
| ```java | ||||
| var wizard = new Wizard(); | ||||
| var goblin = new Goblin(); | ||||
|  | ||||
| goblin.printStatus(); | ||||
| wizard.castSpell(new ShrinkSpell(), goblin); | ||||
| wizard.castSpell(goblin::changeSize); | ||||
| goblin.printStatus(); | ||||
| wizard.castSpell(new InvisibilitySpell(), goblin); | ||||
|  | ||||
| wizard.castSpell(goblin::changeVisibility); | ||||
| goblin.printStatus(); | ||||
|  | ||||
| wizard.undoLastSpell(); | ||||
| goblin.printStatus(); | ||||
|  | ||||
| wizard.undoLastSpell(); | ||||
| goblin.printStatus(); | ||||
|  | ||||
| wizard.redoLastSpell(); | ||||
| goblin.printStatus(); | ||||
|  | ||||
| wizard.redoLastSpell(); | ||||
| goblin.printStatus(); | ||||
| ``` | ||||
|  | ||||
| Here's the program output: | ||||
|  | ||||
| ```java | ||||
| // Goblin, [size=normal] [visibility=visible] | ||||
| // Wizard casts Shrink spell at Goblin | ||||
| // Goblin, [size=small] [visibility=visible] | ||||
| // Wizard casts Invisibility spell at Goblin | ||||
| // Goblin, [size=small] [visibility=invisible] | ||||
| // Wizard undoes Invisibility spell | ||||
| // Goblin, [size=small] [visibility=visible] | ||||
| Goblin, [size=normal] [visibility=visible] | ||||
| Goblin, [size=small] [visibility=visible] | ||||
| Goblin, [size=small] [visibility=invisible] | ||||
| Goblin, [size=small] [visibility=visible] | ||||
| Goblin, [size=normal] [visibility=visible] | ||||
| Goblin, [size=small] [visibility=visible] | ||||
| Goblin, [size=small] [visibility=invisible] | ||||
| ``` | ||||
|  | ||||
| ## Class diagram | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 50 KiB | 
| @@ -1,15 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true" | ||||
|   realizations="true" associations="true" dependencies="false" nesting-relationships="true"> | ||||
|   <class id="1" language="java" name="com.iluwatar.command.ShrinkSpell" project="command"  | ||||
|     file="/command/src/main/java/com/iluwatar/command/ShrinkSpell.java" binary="false" corner="BOTTOM_RIGHT">     | ||||
|     <position height="178" width="141" x="-30" y="681"/>     | ||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  | ||||
|       sort-features="false" accessors="true" visibility="true">       | ||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       | ||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     | ||||
|     </display>   | ||||
|   </class>   | ||||
|   <class id="2" language="java" name="com.iluwatar.command.Goblin" project="command" | ||||
|     file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT"> | ||||
|     <position height="-1" width="-1" x="129" y="1223"/> | ||||
| @@ -28,24 +19,6 @@ | ||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/> | ||||
|     </display> | ||||
|   </class> | ||||
|   <class id="4" language="java" name="com.iluwatar.command.Command" project="command"  | ||||
|     file="/command/src/main/java/com/iluwatar/command/Command.java" binary="false" corner="BOTTOM_RIGHT">     | ||||
|     <position height="-1" width="-1" x="129" y="561"/>     | ||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  | ||||
|       sort-features="false" accessors="true" visibility="true">       | ||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       | ||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     | ||||
|     </display>   | ||||
|   </class>   | ||||
|   <class id="5" language="java" name="com.iluwatar.command.InvisibilitySpell" project="command"  | ||||
|     file="/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java" binary="false" corner="BOTTOM_RIGHT">     | ||||
|     <position height="160" width="141" x="151" y="681"/>     | ||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  | ||||
|       sort-features="false" accessors="true" visibility="true">       | ||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       | ||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     | ||||
|     </display>   | ||||
|   </class>   | ||||
|   <class id="6" language="java" name="com.iluwatar.command.Target" project="command" | ||||
|     file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT"> | ||||
|     <position height="-1" width="-1" x="129" y="1014"/> | ||||
|   | ||||
| @@ -4,33 +4,11 @@ package com.iluwatar.command { | ||||
|     + App() | ||||
|     + main(args : String[]) {static} | ||||
|   } | ||||
|   interface Command { | ||||
|     + Command() | ||||
|     + execute(Target) {abstract} | ||||
|     + redo() {abstract} | ||||
|     + toString() : String {abstract} | ||||
|     + undo() {abstract} | ||||
|   } | ||||
|   class Goblin { | ||||
|     + Goblin() | ||||
|     + toString() : String | ||||
|   } | ||||
|   class InvisibilitySpell { | ||||
|     - target : Target | ||||
|     + InvisibilitySpell() | ||||
|     + execute(target : Target) | ||||
|     + redo() | ||||
|     + toString() : String | ||||
|     + undo() | ||||
|   } | ||||
|   class ShrinkSpell { | ||||
|     - oldSize : Size | ||||
|     - target : Target | ||||
|     + ShrinkSpell() | ||||
|     + execute(target : Target) | ||||
|     + redo() | ||||
|     + toString() : String | ||||
|     + undo() | ||||
|     + changeSize() | ||||
|     + changeVisibility() | ||||
|   } | ||||
|   enum Size { | ||||
|     + NORMAL {static} | ||||
| @@ -62,22 +40,19 @@ package com.iluwatar.command { | ||||
|   } | ||||
|   class Wizard { | ||||
|     - LOGGER : Logger {static} | ||||
|     - redoStack : Deque<Command> | ||||
|     - undoStack : Deque<Command> | ||||
|     - redoStack : Deque<Runnable> | ||||
|     - undoStack : Deque<Runnable> | ||||
|     + Wizard() | ||||
|     + castSpell(command : Command, target : Target) | ||||
|     + castSpell(Runnable : runnable) | ||||
|     + redoLastSpell() | ||||
|     + toString() : String | ||||
|     + undoLastSpell() | ||||
|   } | ||||
| } | ||||
| Target -->  "-size" Size | ||||
| Wizard -->  "-undoStack" Command | ||||
| ShrinkSpell -->  "-oldSize" Size | ||||
| InvisibilitySpell -->  "-target" Target | ||||
| ShrinkSpell -->  "-target" Target | ||||
| Wizard -->  "-changeSize" Goblin | ||||
| Wizard -->  "-changeVisibility" Goblin | ||||
| Target -->  "-visibility" Visibility | ||||
| Goblin --|> Target | ||||
| InvisibilitySpell ..|> Command | ||||
| ShrinkSpell ..|> Command | ||||
| App --> "castSpell" Wizard | ||||
| @enduml | ||||
|   | ||||
| @@ -30,12 +30,10 @@ package com.iluwatar.command; | ||||
|  * | ||||
|  * <p>Four terms always associated with the command pattern are command, receiver, invoker and | ||||
|  * client. A command object (spell) knows about the receiver (target) and invokes a method of the | ||||
|  * receiver. Values for parameters of the receiver method are stored in the command. The receiver | ||||
|  * then does the work. An invoker object (wizard) knows how to execute a command, and optionally | ||||
|  * does bookkeeping about the command execution. The invoker does not know anything about a concrete | ||||
|  * command, it knows only about command interface. Both an invoker object and several command | ||||
|  * objects are held by a client object (app). The client decides which commands to execute at which | ||||
|  * points. To execute a command, it passes the command object to the invoker object. | ||||
|  * receiver. An invoker object (wizard) receives a reference to the command to be executed and | ||||
|  * optionally does bookkeeping about the command execution. The invoker does not know anything | ||||
|  * about how the command is executed. The client decides which commands to execute at which | ||||
|  * points. To execute a command, it passes a reference of the function to the invoker object. | ||||
|  * | ||||
|  * <p>In other words, in this example the wizard casts spells on the goblin. The wizard keeps track | ||||
|  * of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of | ||||
| @@ -54,10 +52,10 @@ public class App { | ||||
|  | ||||
|     goblin.printStatus(); | ||||
|  | ||||
|     wizard.castSpell(new ShrinkSpell(), goblin); | ||||
|     wizard.castSpell(goblin::changeSize); | ||||
|     goblin.printStatus(); | ||||
|  | ||||
|     wizard.castSpell(new InvisibilitySpell(), goblin); | ||||
|     wizard.castSpell(goblin::changeVisibility); | ||||
|     goblin.printStatus(); | ||||
|  | ||||
|     wizard.undoLastSpell(); | ||||
|   | ||||
| @@ -1,37 +0,0 @@ | ||||
| /* | ||||
|  * The MIT License | ||||
|  * Copyright © 2014-2019 Ilkka Seppälä | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package com.iluwatar.command; | ||||
|  | ||||
| /** | ||||
|  * Interface for Commands. | ||||
|  */ | ||||
| public interface Command { | ||||
|   void execute(Target target); | ||||
|  | ||||
|   void undo(); | ||||
|  | ||||
|   void redo(); | ||||
|  | ||||
|   String toString(); | ||||
| } | ||||
| @@ -23,11 +23,16 @@ | ||||
|  | ||||
| package com.iluwatar.command; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Goblin is the target of the spells. | ||||
|  */ | ||||
| public class Goblin extends Target { | ||||
|  | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(Goblin.class); | ||||
|  | ||||
|   public Goblin() { | ||||
|     setSize(Size.NORMAL); | ||||
|     setVisibility(Visibility.VISIBLE); | ||||
| @@ -37,5 +42,4 @@ public class Goblin extends Target { | ||||
|   public String toString() { | ||||
|     return "Goblin"; | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,57 +0,0 @@ | ||||
| /* | ||||
|  * The MIT License | ||||
|  * Copyright © 2014-2019 Ilkka Seppälä | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package com.iluwatar.command; | ||||
|  | ||||
| /** | ||||
|  * InvisibilitySpell is a concrete command. | ||||
|  */ | ||||
| public class InvisibilitySpell implements Command { | ||||
|  | ||||
|   private Target target; | ||||
|  | ||||
|   @Override | ||||
|   public void execute(Target target) { | ||||
|     target.setVisibility(Visibility.INVISIBLE); | ||||
|     this.target = target; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void undo() { | ||||
|     if (target != null) { | ||||
|       target.setVisibility(Visibility.VISIBLE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void redo() { | ||||
|     if (target != null) { | ||||
|       target.setVisibility(Visibility.INVISIBLE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Invisibility spell"; | ||||
|   } | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
|  * The MIT License | ||||
|  * Copyright © 2014-2019 Ilkka Seppälä | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package com.iluwatar.command; | ||||
|  | ||||
| /** | ||||
|  * ShrinkSpell is a concrete command. | ||||
|  */ | ||||
| public class ShrinkSpell implements Command { | ||||
|  | ||||
|   private Size oldSize; | ||||
|   private Target target; | ||||
|  | ||||
|   @Override | ||||
|   public void execute(Target target) { | ||||
|     oldSize = target.getSize(); | ||||
|     target.setSize(Size.SMALL); | ||||
|     this.target = target; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void undo() { | ||||
|     if (oldSize != null && target != null) { | ||||
|       var temp = target.getSize(); | ||||
|       target.setSize(oldSize); | ||||
|       oldSize = temp; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void redo() { | ||||
|     undo(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Shrink spell"; | ||||
|   } | ||||
| } | ||||
| @@ -62,4 +62,21 @@ public abstract class Target { | ||||
|   public void printStatus() { | ||||
|     LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Changes the size of the target. | ||||
|    */ | ||||
|   public void changeSize() { | ||||
|     var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; | ||||
|     setSize(oldSize); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Changes the visibility of the target. | ||||
|    */ | ||||
|   public void changeVisibility() { | ||||
|     var visible = getVisibility() == Visibility.INVISIBLE | ||||
|             ? Visibility.VISIBLE : Visibility.INVISIBLE; | ||||
|     setVisibility(visible); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -35,20 +35,18 @@ public class Wizard { | ||||
|  | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); | ||||
|  | ||||
|   private final Deque<Command> undoStack = new LinkedList<>(); | ||||
|   private final Deque<Command> redoStack = new LinkedList<>(); | ||||
|   private final Deque<Runnable> undoStack = new LinkedList<>(); | ||||
|   private final Deque<Runnable> redoStack = new LinkedList<>(); | ||||
|  | ||||
|   public Wizard() { | ||||
|     // comment to ignore sonar issue: LEVEL critical | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Cast spell. | ||||
|    */ | ||||
|   public void castSpell(Command command, Target target) { | ||||
|     LOGGER.info("{} casts {} at {}", this, command, target); | ||||
|     command.execute(target); | ||||
|     undoStack.offerLast(command); | ||||
|   public void castSpell(Runnable runnable) { | ||||
|     runnable.run(); | ||||
|     undoStack.offerLast(runnable); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -58,8 +56,7 @@ public class Wizard { | ||||
|     if (!undoStack.isEmpty()) { | ||||
|       var previousSpell = undoStack.pollLast(); | ||||
|       redoStack.offerLast(previousSpell); | ||||
|       LOGGER.info("{} undoes {}", this, previousSpell); | ||||
|       previousSpell.undo(); | ||||
|       previousSpell.run(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -70,8 +67,7 @@ public class Wizard { | ||||
|     if (!redoStack.isEmpty()) { | ||||
|       var previousSpell = redoStack.pollLast(); | ||||
|       undoStack.offerLast(previousSpell); | ||||
|       LOGGER.info("{} redoes {}", this, previousSpell); | ||||
|       previousSpell.redo(); | ||||
|       previousSpell.run(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -56,10 +56,10 @@ public class CommandTest { | ||||
|     var wizard = new Wizard(); | ||||
|     var goblin = new Goblin(); | ||||
|  | ||||
|     wizard.castSpell(new ShrinkSpell(), goblin); | ||||
|     wizard.castSpell(goblin::changeSize); | ||||
|     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE); | ||||
|  | ||||
|     wizard.castSpell(new InvisibilitySpell(), goblin); | ||||
|     wizard.castSpell(goblin::changeVisibility); | ||||
|     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE); | ||||
|  | ||||
|     wizard.undoLastSpell(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user