Merge pull request #1531 from ravening/command-functional
Refactor the command pattern to use lambda functions
This commit is contained in:
		| @@ -14,14 +14,14 @@ Action, Transaction | |||||||
|  |  | ||||||
| ## Intent | ## Intent | ||||||
|  |  | ||||||
| Encapsulate a request as an object, thereby letting you parameterize clients with different  | Encapsulate a request as an object, thereby letting you parameterize clients with different | ||||||
| requests, queue or log requests, and support undoable operations. | requests, queue or log requests, and support undoable operations. | ||||||
|  |  | ||||||
| ## Explanation | ## Explanation | ||||||
| Real world example | Real world example | ||||||
|  |  | ||||||
| > There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.  | > There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. | ||||||
| > The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses  | > The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses | ||||||
| > the spells one by one. Each spell here is a command object that can be undone. | > the spells one by one. Each spell here is a command object that can be undone. | ||||||
|  |  | ||||||
| In plain words | In plain words | ||||||
| @@ -30,8 +30,8 @@ In plain words | |||||||
|  |  | ||||||
| Wikipedia says | Wikipedia says | ||||||
|  |  | ||||||
| > In object-oriented programming, the command pattern is a behavioral design pattern in which an  | > In object-oriented programming, the command pattern is a behavioral design pattern in which an | ||||||
| > object is used to encapsulate all information needed to perform an action or trigger an event at  | > object is used to encapsulate all information needed to perform an action or trigger an event at | ||||||
| > a later time. | > a later time. | ||||||
|  |  | ||||||
| **Programmatic Example** | **Programmatic Example** | ||||||
| @@ -48,18 +48,16 @@ public class Wizard { | |||||||
|  |  | ||||||
|   public Wizard() {} |   public Wizard() {} | ||||||
|  |  | ||||||
|   public void castSpell(Command command, Target target) { |   public void castSpell(Runnable runnable) { | ||||||
|     LOGGER.info("{} casts {} at {}", this, command, target); |     runnable.run(); | ||||||
|     command.execute(target); |     undoStack.offerLast(runnable); | ||||||
|     undoStack.offerLast(command); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void undoLastSpell() { |   public void undoLastSpell() { | ||||||
|     if (!undoStack.isEmpty()) { |     if (!undoStack.isEmpty()) { | ||||||
|       var previousSpell = undoStack.pollLast(); |       var previousSpell = undoStack.pollLast(); | ||||||
|       redoStack.offerLast(previousSpell); |       redoStack.offerLast(previousSpell); | ||||||
|       LOGGER.info("{} undoes {}", this, previousSpell); |       previousSpell.run(); | ||||||
|       previousSpell.undo(); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -67,8 +65,7 @@ public class Wizard { | |||||||
|     if (!redoStack.isEmpty()) { |     if (!redoStack.isEmpty()) { | ||||||
|       var previousSpell = redoStack.pollLast(); |       var previousSpell = redoStack.pollLast(); | ||||||
|       undoStack.offerLast(previousSpell); |       undoStack.offerLast(previousSpell); | ||||||
|       LOGGER.info("{} redoes {}", this, previousSpell); |       previousSpell.run(); | ||||||
|       previousSpell.redo(); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -79,84 +76,7 @@ public class Wizard { | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Next we present the spell hierarchy. | Next, we have the goblin who's the target of the spells. | ||||||
|  |  | ||||||
| ```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. |  | ||||||
|  |  | ||||||
| ```java | ```java | ||||||
| public abstract class Target { | public abstract class Target { | ||||||
| @@ -203,33 +123,73 @@ public class Goblin extends Target { | |||||||
|     return "Goblin"; |     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. | Here's the whole example in action. | ||||||
|  |  | ||||||
| ```java | ```java | ||||||
| var wizard = new Wizard(); | var wizard = new Wizard(); | ||||||
| var goblin = new Goblin(); | var goblin = new Goblin(); | ||||||
|  |  | ||||||
| goblin.printStatus(); | goblin.printStatus(); | ||||||
| wizard.castSpell(new ShrinkSpell(), goblin); | wizard.castSpell(goblin::changeSize); | ||||||
| goblin.printStatus(); | goblin.printStatus(); | ||||||
| wizard.castSpell(new InvisibilitySpell(), goblin); |  | ||||||
|  | wizard.castSpell(goblin::changeVisibility); | ||||||
| goblin.printStatus(); | goblin.printStatus(); | ||||||
|  |  | ||||||
| wizard.undoLastSpell(); | wizard.undoLastSpell(); | ||||||
| goblin.printStatus(); | goblin.printStatus(); | ||||||
|  |  | ||||||
|  | wizard.undoLastSpell(); | ||||||
|  | goblin.printStatus(); | ||||||
|  |  | ||||||
|  | wizard.redoLastSpell(); | ||||||
|  | goblin.printStatus(); | ||||||
|  |  | ||||||
|  | wizard.redoLastSpell(); | ||||||
|  | goblin.printStatus(); | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Here's the program output: | Here's the program output: | ||||||
|  |  | ||||||
| ```java | ```java | ||||||
| // Goblin, [size=normal] [visibility=visible] | Goblin, [size=normal] [visibility=visible] | ||||||
| // Wizard casts Shrink spell at Goblin | Goblin, [size=small] [visibility=visible] | ||||||
| // Goblin, [size=small] [visibility=visible] | Goblin, [size=small] [visibility=invisible] | ||||||
| // Wizard casts Invisibility spell at Goblin | Goblin, [size=small] [visibility=visible] | ||||||
| // Goblin, [size=small] [visibility=invisible] | Goblin, [size=normal] [visibility=visible] | ||||||
| // Wizard undoes Invisibility spell | Goblin, [size=small] [visibility=visible] | ||||||
| // Goblin, [size=small] [visibility=visible] | Goblin, [size=small] [visibility=invisible] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Class diagram | ## Class diagram | ||||||
| @@ -240,26 +200,26 @@ Here's the program output: | |||||||
|  |  | ||||||
| Use the Command pattern when you want to: | Use the Command pattern when you want to: | ||||||
|  |  | ||||||
| * Parameterize objects by an action to perform. You can express such parameterization in a  | * Parameterize objects by an action to perform. You can express such parameterization in a | ||||||
| procedural language with a callback function, that is, a function that's registered somewhere to be  | procedural language with a callback function, that is, a function that's registered somewhere to be | ||||||
| called at a later point. Commands are an object-oriented replacement for callbacks. | called at a later point. Commands are an object-oriented replacement for callbacks. | ||||||
| * Specify, queue, and execute requests at different times. A Command object can have a lifetime  | * Specify, queue, and execute requests at different times. A Command object can have a lifetime | ||||||
| independent of the original request. If the receiver of a request can be represented in an address  | independent of the original request. If the receiver of a request can be represented in an address | ||||||
| space-independent way, then you can transfer a command object for the request to a different process  | space-independent way, then you can transfer a command object for the request to a different process | ||||||
| and fulfill the request there. | and fulfill the request there. | ||||||
| * Support undo. The Command's execute operation can store state for reversing its effects in the  | * Support undo. The Command's execute operation can store state for reversing its effects in the | ||||||
| command itself. The Command interface must have an added un-execute operation that reverses the  | command itself. The Command interface must have an added un-execute operation that reverses the | ||||||
| effects of a previous call to execute. The executed commands are stored in a history list.  | effects of a previous call to execute. The executed commands are stored in a history list. | ||||||
| Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling  | Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling | ||||||
| un-execute and execute, respectively. | un-execute and execute, respectively. | ||||||
| * Support logging changes so that they can be reapplied in case of a system crash. By augmenting the  | * Support logging changes so that they can be reapplied in case of a system crash. By augmenting the | ||||||
| Command interface with load and store operations, you can keep a persistent log of changes.  | Command interface with load and store operations, you can keep a persistent log of changes. | ||||||
| Recovering from a crash involves reloading logged commands from disk and re-executing them with  | Recovering from a crash involves reloading logged commands from disk and re-executing them with | ||||||
| the execute operation. | the execute operation. | ||||||
| * Structure a system around high-level operations build on primitive operations. Such a structure is  | * Structure a system around high-level operations build on primitive operations. Such a structure is | ||||||
| common in information systems that support transactions. A transaction encapsulates a set of changes  | common in information systems that support transactions. A transaction encapsulates a set of changes | ||||||
| to data. The Command pattern offers a way to model transactions. Commands have a common interface,  | to data. The Command pattern offers a way to model transactions. Commands have a common interface, | ||||||
| letting you invoke all transactions the same way. The pattern also makes it easy to extend the  | letting you invoke all transactions the same way. The pattern also makes it easy to extend the | ||||||
| system with new transactions. | system with new transactions. | ||||||
|  |  | ||||||
| ## Typical Use Case | ## Typical Use Case | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 50 KiB | 
| @@ -1,116 +1,89 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"  | <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">   |   realizations="true" associations="true" dependencies="false" nesting-relationships="true"> | ||||||
|   <class id="1" language="java" name="com.iluwatar.command.ShrinkSpell" project="command"  |   <class id="2" language="java" name="com.iluwatar.command.Goblin" project="command" | ||||||
|     file="/command/src/main/java/com/iluwatar/command/ShrinkSpell.java" binary="false" corner="BOTTOM_RIGHT">     |     file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT"> | ||||||
|     <position height="178" width="141" x="-30" y="681"/>     |     <position height="-1" width="-1" x="129" y="1223"/> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||||||
|       sort-features="false" accessors="true" visibility="true">       |       sort-features="false" accessors="true" visibility="true"> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |       <attributes public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |       <operations public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|     </display>   |     </display> | ||||||
|   </class>   |   </class> | ||||||
|   <class id="2" language="java" name="com.iluwatar.command.Goblin" project="command"  |   <class id="3" language="java" name="com.iluwatar.command.Wizard" project="command" | ||||||
|     file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">     |     file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT"> | ||||||
|     <position height="-1" width="-1" x="129" y="1223"/>     |     <position height="-1" width="-1" x="129" y="362"/> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||||||
|       sort-features="false" accessors="true" visibility="true">       |       sort-features="false" accessors="true" visibility="true"> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |       <attributes public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |       <operations public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|     </display>   |     </display> | ||||||
|   </class>   |   </class> | ||||||
|   <class id="3" language="java" name="com.iluwatar.command.Wizard" project="command"  |   <class id="6" language="java" name="com.iluwatar.command.Target" project="command" | ||||||
|     file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT">     |     file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT"> | ||||||
|     <position height="-1" width="-1" x="129" y="362"/>     |     <position height="-1" width="-1" x="129" y="1014"/> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||||||
|       sort-features="false" accessors="true" visibility="true">       |       sort-features="false" accessors="true" visibility="true"> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |       <attributes public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |       <operations public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|     </display>   |     </display> | ||||||
|   </class>   |   </class> | ||||||
|   <class id="4" language="java" name="com.iluwatar.command.Command" project="command"  |   <association id="7"> | ||||||
|     file="/command/src/main/java/com/iluwatar/command/Command.java" binary="false" corner="BOTTOM_RIGHT">     |     <end type="SOURCE" refId="3" navigable="false"> | ||||||
|     <position height="-1" width="-1" x="129" y="561"/>     |       <attribute id="8" name="redoStack"> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |         <position height="20" width="67" x="140" y="451"/> | ||||||
|       sort-features="false" accessors="true" visibility="true">       |       </attribute> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |       <multiplicity id="9" minimum="0" maximum="2147483647"> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |         <position height="18" width="25" x="221" y="452"/> | ||||||
|     </display>   |       </multiplicity> | ||||||
|   </class>   |     </end> | ||||||
|   <class id="5" language="java" name="com.iluwatar.command.InvisibilitySpell" project="command"  |     <end type="TARGET" refId="4" navigable="true"/> | ||||||
|     file="/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java" binary="false" corner="BOTTOM_RIGHT">     |     <display labels="true" multiplicity="true"/> | ||||||
|     <position height="160" width="141" x="151" y="681"/>     |   </association> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |   <generalization id="10"> | ||||||
|       sort-features="false" accessors="true" visibility="true">       |     <end type="SOURCE" refId="2"/> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |     <end type="TARGET" refId="6"/> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |   </generalization> | ||||||
|     </display>   |   <association id="11"> | ||||||
|   </class>   |     <end type="SOURCE" refId="1" navigable="false"> | ||||||
|   <class id="6" language="java" name="com.iluwatar.command.Target" project="command"  |       <attribute id="12" name="target"/> | ||||||
|     file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">     |       <multiplicity id="13" minimum="0" maximum="1"/> | ||||||
|     <position height="-1" width="-1" x="129" y="1014"/>     |     </end> | ||||||
|     <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"  |     <end type="TARGET" refId="6" navigable="true"/> | ||||||
|       sort-features="false" accessors="true" visibility="true">       |     <display labels="true" multiplicity="true"/> | ||||||
|       <attributes public="true" package="true" protected="true" private="true" static="true"/>       |   </association> | ||||||
|       <operations public="true" package="true" protected="true" private="true" static="true"/>     |   <generalization id="14"> | ||||||
|     </display>   |     <end type="SOURCE" refId="1"/> | ||||||
|   </class>   |     <end type="TARGET" refId="4"/> | ||||||
|   <association id="7">     |   </generalization> | ||||||
|     <end type="SOURCE" refId="3" navigable="false">       |   <association id="15"> | ||||||
|       <attribute id="8" name="redoStack">         |     <end type="SOURCE" refId="3" navigable="false"> | ||||||
|         <position height="20" width="67" x="140" y="451"/>       |       <attribute id="16" name="undoStack"> | ||||||
|       </attribute>       |         <position height="20" width="70" x="-17" y="451"/> | ||||||
|       <multiplicity id="9" minimum="0" maximum="2147483647">         |       </attribute> | ||||||
|         <position height="18" width="25" x="221" y="452"/>       |       <multiplicity id="17" minimum="0" maximum="2147483647"> | ||||||
|       </multiplicity>     |         <position height="18" width="25" x="60" y="452"/> | ||||||
|     </end>     |       </multiplicity> | ||||||
|     <end type="TARGET" refId="4" navigable="true"/>     |     </end> | ||||||
|     <display labels="true" multiplicity="true"/>   |     <end type="TARGET" refId="4" navigable="true"/> | ||||||
|   </association>   |     <display labels="true" multiplicity="true"/> | ||||||
|   <generalization id="10">     |   </association> | ||||||
|     <end type="SOURCE" refId="2"/>     |   <generalization id="18"> | ||||||
|     <end type="TARGET" refId="6"/>   |     <end type="SOURCE" refId="5"/> | ||||||
|   </generalization>   |     <end type="TARGET" refId="4"/> | ||||||
|   <association id="11">     |   </generalization> | ||||||
|     <end type="SOURCE" refId="1" navigable="false">       |   <association id="19"> | ||||||
|       <attribute id="12" name="target"/>       |     <end type="SOURCE" refId="5" navigable="false"> | ||||||
|       <multiplicity id="13" minimum="0" maximum="1"/>     |       <attribute id="20" name="target"/> | ||||||
|     </end>     |       <multiplicity id="21" minimum="0" maximum="1"/> | ||||||
|     <end type="TARGET" refId="6" navigable="true"/>     |     </end> | ||||||
|     <display labels="true" multiplicity="true"/>   |     <end type="TARGET" refId="6" navigable="true"/> | ||||||
|   </association>   |     <display labels="true" multiplicity="true"/> | ||||||
|   <generalization id="14">     |   </association> | ||||||
|     <end type="SOURCE" refId="1"/>     |   <classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||||||
|     <end type="TARGET" refId="4"/>   |     sort-features="false" accessors="true" visibility="true"> | ||||||
|   </generalization>   |     <attributes public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|   <association id="15">     |     <operations public="true" package="true" protected="true" private="true" static="true"/> | ||||||
|     <end type="SOURCE" refId="3" navigable="false">       |   </classifier-display> | ||||||
|       <attribute id="16" name="undoStack">         |  | ||||||
|         <position height="20" width="70" x="-17" y="451"/>       |  | ||||||
|       </attribute>       |  | ||||||
|       <multiplicity id="17" minimum="0" maximum="2147483647">         |  | ||||||
|         <position height="18" width="25" x="60" y="452"/>       |  | ||||||
|       </multiplicity>     |  | ||||||
|     </end>     |  | ||||||
|     <end type="TARGET" refId="4" navigable="true"/>     |  | ||||||
|     <display labels="true" multiplicity="true"/>   |  | ||||||
|   </association>   |  | ||||||
|   <generalization id="18">     |  | ||||||
|     <end type="SOURCE" refId="5"/>     |  | ||||||
|     <end type="TARGET" refId="4"/>   |  | ||||||
|   </generalization>   |  | ||||||
|   <association id="19">     |  | ||||||
|     <end type="SOURCE" refId="5" navigable="false">       |  | ||||||
|       <attribute id="20" name="target"/>       |  | ||||||
|       <multiplicity id="21" minimum="0" maximum="1"/>     |  | ||||||
|     </end>     |  | ||||||
|     <end type="TARGET" refId="6" navigable="true"/>     |  | ||||||
|     <display labels="true" multiplicity="true"/>   |  | ||||||
|   </association>   |  | ||||||
|   <classifier-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"/>   |  | ||||||
|   </classifier-display>   |  | ||||||
|   <association-display labels="true" multiplicity="true"/> |   <association-display labels="true" multiplicity="true"/> | ||||||
| </class-diagram> | </class-diagram> | ||||||
|   | |||||||
| @@ -4,33 +4,11 @@ package com.iluwatar.command { | |||||||
|     + App() |     + App() | ||||||
|     + main(args : String[]) {static} |     + main(args : String[]) {static} | ||||||
|   } |   } | ||||||
|   interface Command { |  | ||||||
|     + Command() |  | ||||||
|     + execute(Target) {abstract} |  | ||||||
|     + redo() {abstract} |  | ||||||
|     + toString() : String {abstract} |  | ||||||
|     + undo() {abstract} |  | ||||||
|   } |  | ||||||
|   class Goblin { |   class Goblin { | ||||||
|     + Goblin() |     + Goblin() | ||||||
|     + toString() : String |     + toString() : String | ||||||
|   } |     + changeSize() | ||||||
|   class InvisibilitySpell { |     + changeVisibility() | ||||||
|     - target : Target |  | ||||||
|     + InvisibilitySpell() |  | ||||||
|     + execute(target : Target) |  | ||||||
|     + redo() |  | ||||||
|     + toString() : String |  | ||||||
|     + undo() |  | ||||||
|   } |  | ||||||
|   class ShrinkSpell { |  | ||||||
|     - oldSize : Size |  | ||||||
|     - target : Target |  | ||||||
|     + ShrinkSpell() |  | ||||||
|     + execute(target : Target) |  | ||||||
|     + redo() |  | ||||||
|     + toString() : String |  | ||||||
|     + undo() |  | ||||||
|   } |   } | ||||||
|   enum Size { |   enum Size { | ||||||
|     + NORMAL {static} |     + NORMAL {static} | ||||||
| @@ -62,22 +40,19 @@ package com.iluwatar.command { | |||||||
|   } |   } | ||||||
|   class Wizard { |   class Wizard { | ||||||
|     - LOGGER : Logger {static} |     - LOGGER : Logger {static} | ||||||
|     - redoStack : Deque<Command> |     - redoStack : Deque<Runnable> | ||||||
|     - undoStack : Deque<Command> |     - undoStack : Deque<Runnable> | ||||||
|     + Wizard() |     + Wizard() | ||||||
|     + castSpell(command : Command, target : Target) |     + castSpell(Runnable : runnable) | ||||||
|     + redoLastSpell() |     + redoLastSpell() | ||||||
|     + toString() : String |     + toString() : String | ||||||
|     + undoLastSpell() |     + undoLastSpell() | ||||||
|   } |   } | ||||||
| } | } | ||||||
| Target -->  "-size" Size | Target -->  "-size" Size | ||||||
| Wizard -->  "-undoStack" Command | Wizard -->  "-changeSize" Goblin | ||||||
| ShrinkSpell -->  "-oldSize" Size | Wizard -->  "-changeVisibility" Goblin | ||||||
| InvisibilitySpell -->  "-target" Target |  | ||||||
| ShrinkSpell -->  "-target" Target |  | ||||||
| Target -->  "-visibility" Visibility | Target -->  "-visibility" Visibility | ||||||
| Goblin --|> Target | Goblin --|> Target | ||||||
| InvisibilitySpell ..|> Command | App --> "castSpell" Wizard | ||||||
| ShrinkSpell ..|> Command |  | ||||||
| @enduml | @enduml | ||||||
|   | |||||||
| @@ -30,12 +30,10 @@ package com.iluwatar.command; | |||||||
|  * |  * | ||||||
|  * <p>Four terms always associated with the command pattern are command, receiver, invoker and |  * <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 |  * 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 |  * receiver. An invoker object (wizard) receives a reference to the command to be executed and | ||||||
|  * then does the work. An invoker object (wizard) knows how to execute a command, and optionally |  * optionally does bookkeeping about the command execution. The invoker does not know anything | ||||||
|  * does bookkeeping about the command execution. The invoker does not know anything about a concrete |  * about how the command is executed. The client decides which commands to execute at which | ||||||
|  * command, it knows only about command interface. Both an invoker object and several command |  * points. To execute a command, it passes a reference of the function to the invoker object. | ||||||
|  * 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. |  | ||||||
|  * |  * | ||||||
|  * <p>In other words, in this example the wizard casts spells on the goblin. The wizard keeps track |  * <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 |  * 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(); |     goblin.printStatus(); | ||||||
|  |  | ||||||
|     wizard.castSpell(new ShrinkSpell(), goblin); |     wizard.castSpell(goblin::changeSize); | ||||||
|     goblin.printStatus(); |     goblin.printStatus(); | ||||||
|  |  | ||||||
|     wizard.castSpell(new InvisibilitySpell(), goblin); |     wizard.castSpell(goblin::changeVisibility); | ||||||
|     goblin.printStatus(); |     goblin.printStatus(); | ||||||
|  |  | ||||||
|     wizard.undoLastSpell(); |     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; | package com.iluwatar.command; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Goblin is the target of the spells. |  * Goblin is the target of the spells. | ||||||
|  */ |  */ | ||||||
| public class Goblin extends Target { | public class Goblin extends Target { | ||||||
|  |  | ||||||
|  |   private static final Logger LOGGER = LoggerFactory.getLogger(Goblin.class); | ||||||
|  |  | ||||||
|   public Goblin() { |   public Goblin() { | ||||||
|     setSize(Size.NORMAL); |     setSize(Size.NORMAL); | ||||||
|     setVisibility(Visibility.VISIBLE); |     setVisibility(Visibility.VISIBLE); | ||||||
| @@ -37,5 +42,4 @@ public class Goblin extends Target { | |||||||
|   public String toString() { |   public String toString() { | ||||||
|     return "Goblin"; |     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() { |   public void printStatus() { | ||||||
|     LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); |     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 static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); | ||||||
|  |  | ||||||
|   private final Deque<Command> undoStack = new LinkedList<>(); |   private final Deque<Runnable> undoStack = new LinkedList<>(); | ||||||
|   private final Deque<Command> redoStack = new LinkedList<>(); |   private final Deque<Runnable> redoStack = new LinkedList<>(); | ||||||
|  |  | ||||||
|   public Wizard() { |   public Wizard() { | ||||||
|     // comment to ignore sonar issue: LEVEL critical |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Cast spell. |    * Cast spell. | ||||||
|    */ |    */ | ||||||
|   public void castSpell(Command command, Target target) { |   public void castSpell(Runnable runnable) { | ||||||
|     LOGGER.info("{} casts {} at {}", this, command, target); |     runnable.run(); | ||||||
|     command.execute(target); |     undoStack.offerLast(runnable); | ||||||
|     undoStack.offerLast(command); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -58,8 +56,7 @@ public class Wizard { | |||||||
|     if (!undoStack.isEmpty()) { |     if (!undoStack.isEmpty()) { | ||||||
|       var previousSpell = undoStack.pollLast(); |       var previousSpell = undoStack.pollLast(); | ||||||
|       redoStack.offerLast(previousSpell); |       redoStack.offerLast(previousSpell); | ||||||
|       LOGGER.info("{} undoes {}", this, previousSpell); |       previousSpell.run(); | ||||||
|       previousSpell.undo(); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -70,8 +67,7 @@ public class Wizard { | |||||||
|     if (!redoStack.isEmpty()) { |     if (!redoStack.isEmpty()) { | ||||||
|       var previousSpell = redoStack.pollLast(); |       var previousSpell = redoStack.pollLast(); | ||||||
|       undoStack.offerLast(previousSpell); |       undoStack.offerLast(previousSpell); | ||||||
|       LOGGER.info("{} redoes {}", this, previousSpell); |       previousSpell.run(); | ||||||
|       previousSpell.redo(); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,10 +56,10 @@ public class CommandTest { | |||||||
|     var wizard = new Wizard(); |     var wizard = new Wizard(); | ||||||
|     var goblin = new Goblin(); |     var goblin = new Goblin(); | ||||||
|  |  | ||||||
|     wizard.castSpell(new ShrinkSpell(), goblin); |     wizard.castSpell(goblin::changeSize); | ||||||
|     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE); |     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE); | ||||||
|  |  | ||||||
|     wizard.castSpell(new InvisibilitySpell(), goblin); |     wizard.castSpell(goblin::changeVisibility); | ||||||
|     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE); |     verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE); | ||||||
|  |  | ||||||
|     wizard.undoLastSpell(); |     wizard.undoLastSpell(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user