#95 Updated and clarified Command pattern example

This commit is contained in:
Ilkka Seppala 2015-06-30 17:52:53 +03:00
parent fb446c2991
commit db6ec3cc1a
9 changed files with 224 additions and 237 deletions

View File

@ -299,7 +299,7 @@ A programming idiom is a means of expressing a recurring construct in one or mor
## <a name="command">Command</a> [&#8593;](#list-of-design-patterns) ## <a name="command">Command</a> [&#8593;](#list-of-design-patterns)
**Intent:** Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. **Intent:** Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
![alt text](https://github.com/iluwatar/java-design-patterns/blob/master/command/etc/command_1.png "Command") ![alt text](https://github.com/iluwatar/java-design-patterns/blob/master/command/etc/command.png "Command")
**Applicability:** Use the Command pattern when you want to **Applicability:** Use the Command pattern when you want to

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -3,7 +3,7 @@
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="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"> file="/command/src/main/java/com/iluwatar/command/ShrinkSpell.java" binary="false" corner="BOTTOM_RIGHT">
<position height="160" width="141" x="-171" y="634"/> <position height="178" width="141" x="-30" y="681"/>
<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"/>
@ -12,7 +12,7 @@
</class> </class>
<class id="2" language="java" name="com.iluwatar.command.Goblin" project="command" <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"> file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">
<position height="106" width="138" x="13" y="1069"/> <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"/>
@ -21,7 +21,7 @@
</class> </class>
<class id="3" language="java" name="com.iluwatar.command.Wizard" project="command" <class id="3" language="java" name="com.iluwatar.command.Wizard" 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/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
<position height="160" width="212" x="91" y="235"/> <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"/>
@ -30,7 +30,7 @@
</class> </class>
<class id="4" language="java" name="com.iluwatar.command.Command" project="command" <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"> file="/command/src/main/java/com/iluwatar/command/Command.java" binary="false" corner="BOTTOM_RIGHT">
<position height="159" width="142" x="9" y="435"/> <position height="-1" width="-1" x="129" y="561"/>
<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"/>
@ -39,66 +39,58 @@
</class> </class>
<class id="5" language="java" name="com.iluwatar.command.InvisibilitySpell" project="command" <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"> file="/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java" binary="false" corner="BOTTOM_RIGHT">
<position height="160" width="141" x="10" y="634"/> <position height="160" width="141" x="151" y="681"/>
<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>
<enumeration id="6" language="java" name="com.iluwatar.command.Visibility" project="command" <class id="6" language="java" name="com.iluwatar.command.Target" project="command"
file="/command/src/main/java/com/iluwatar/command/Visibility.java" binary="false" corner="BOTTOM_RIGHT">
<position height="178" width="157" x="191" y="1069"/>
<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>
</enumeration>
<enumeration id="7" language="java" name="com.iluwatar.command.Size" project="command"
file="/command/src/main/java/com/iluwatar/command/Size.java" binary="false" corner="BOTTOM_RIGHT">
<position height="196" width="144" x="-171" y="1069"/>
<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>
</enumeration>
<class id="8" language="java" name="com.iluwatar.command.Target" project="command"
file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT"> file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">
<position height="195" width="176" x="13" y="834"/> <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>
<association id="9"> <association id="7">
<end type="SOURCE" refId="1" navigable="false"> <end type="SOURCE" refId="3" navigable="false">
<attribute id="10" name="target"/> <attribute id="8" name="redoStack">
<multiplicity id="11" minimum="0" maximum="1"/> <position height="20" width="67" x="140" y="451"/>
</attribute>
<multiplicity id="9" minimum="0" maximum="2147483647">
<position height="18" width="25" x="221" y="452"/>
</multiplicity>
</end> </end>
<end type="TARGET" refId="8" navigable="true"/> <end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/> <display labels="true" multiplicity="true"/>
</association> </association>
<generalization id="12"> <generalization id="10">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="6"/>
</generalization>
<association id="11">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="12" name="target"/>
<multiplicity id="13" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="14">
<end type="SOURCE" refId="1"/> <end type="SOURCE" refId="1"/>
<end type="TARGET" refId="4"/> <end type="TARGET" refId="4"/>
</generalization> </generalization>
<generalization id="13">
<bendpoint x="181" y="435"/>
<bendpoint x="181" y="634"/>
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="8"/>
</generalization>
<generalization id="14">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="8"/>
</generalization>
<association id="15"> <association id="15">
<end type="SOURCE" refId="3" navigable="false"> <end type="SOURCE" refId="3" navigable="false">
<attribute id="16" name="undoStack"/> <attribute id="16" name="undoStack">
<multiplicity id="17" minimum="0" maximum="2147483647"/> <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>
<end type="TARGET" refId="4" navigable="true"/> <end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/> <display labels="true" multiplicity="true"/>
@ -112,42 +104,9 @@
<attribute id="20" name="target"/> <attribute id="20" name="target"/>
<multiplicity id="21" minimum="0" maximum="1"/> <multiplicity id="21" minimum="0" maximum="1"/>
</end> </end>
<end type="TARGET" refId="8" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="22">
<end type="SOURCE" refId="8" navigable="false">
<attribute id="23" name="visibility"/>
<multiplicity id="24" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="6" navigable="true"/> <end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/> <display labels="true" multiplicity="true"/>
</association> </association>
<association id="25">
<end type="SOURCE" refId="8" navigable="false">
<attribute id="26" name="size"/>
<multiplicity id="27" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="7" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="28">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="29" name="redoStack"/>
<multiplicity id="30" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="31">
<bendpoint x="-162" y="834"/>
<end type="SOURCE" refId="1" navigable="false">
<attribute id="32" name="oldSize"/>
<multiplicity id="33" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="7" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" <classifier-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"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,36 +1,47 @@
package com.iluwatar.command; package com.iluwatar.command;
/** /**
* *
* In Command pattern actions are objects that can be executed and undone. * In Command pattern actions are objects that can be executed and undone.
* *
* In this example the commands are the spells cast by the wizard on the goblin. * Four terms always associated with the command pattern are command, receiver, invoker and client. A command
* * object (spell) knows about 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)
public class App { * 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
public static void main(String[] args) { * and several command objects are held by a client object (app). The client decides which commands to execute at
Wizard wizard = new Wizard(); * which points. To execute a command, it passes the command object to the invoker object.
Goblin goblin = new Goblin(); *
* In other words, in this example the wizard casts spells on the goblin. The wizard keeps track of the previous
goblin.printStatus(); * spells cast, so it is easy to undo them. In addition, the wizard keeps track of the spells undone, so they
* can be redone.
wizard.castSpell(new ShrinkSpell(), goblin); *
goblin.printStatus(); *
*/
wizard.castSpell(new InvisibilitySpell(), goblin); public class App {
goblin.printStatus();
public static void main(String[] args) {
wizard.undoLastSpell(); Wizard wizard = new Wizard();
goblin.printStatus(); Goblin goblin = new Goblin();
wizard.undoLastSpell(); goblin.printStatus();
goblin.printStatus();
wizard.castSpell(new ShrinkSpell(), goblin);
wizard.redoLastSpell(); goblin.printStatus();
goblin.printStatus();
wizard.castSpell(new InvisibilitySpell(), goblin);
wizard.redoLastSpell(); goblin.printStatus();
goblin.printStatus();
} wizard.undoLastSpell();
} goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
}
}

View File

@ -1,15 +1,20 @@
package com.iluwatar.command; package com.iluwatar.command;
public class Goblin extends Target { /**
*
public Goblin() { * Goblin is the target of the spells
setSize(Size.NORMAL); *
setVisibility(Visibility.VISIBLE); */
} public class Goblin extends Target {
@Override public Goblin() {
public String toString() { setSize(Size.NORMAL);
return "Goblin"; setVisibility(Visibility.VISIBLE);
} }
} @Override
public String toString() {
return "Goblin";
}
}

View File

@ -1,31 +1,36 @@
package com.iluwatar.command; package com.iluwatar.command;
public class InvisibilitySpell extends Command { /**
*
private Target target; * InvisibilitySpell is a concrete command
*
@Override */
public void execute(Target target) { public class InvisibilitySpell extends Command {
target.setVisibility(Visibility.INVISIBLE);
this.target = target; private Target target;
}
@Override
@Override public void execute(Target target) {
public void undo() { target.setVisibility(Visibility.INVISIBLE);
if (target != null) { this.target = target;
target.setVisibility(Visibility.VISIBLE); }
}
} @Override
public void undo() {
@Override if (target != null) {
public void redo() { target.setVisibility(Visibility.VISIBLE);
if (target != null) { }
target.setVisibility(Visibility.INVISIBLE); }
}
} @Override
public void redo() {
@Override if (target != null) {
public String toString() { target.setVisibility(Visibility.INVISIBLE);
return "Invisibility spell"; }
} }
}
@Override
public String toString() {
return "Invisibility spell";
}
}

View File

@ -1,33 +1,38 @@
package com.iluwatar.command; package com.iluwatar.command;
public class ShrinkSpell extends Command { /**
*
private Size oldSize; * ShrinkSpell is a concrete command
private Target target; *
*/
@Override public class ShrinkSpell extends Command {
public void execute(Target target) {
oldSize = target.getSize(); private Size oldSize;
target.setSize(Size.SMALL); private Target target;
this.target = target;
} @Override
public void execute(Target target) {
@Override oldSize = target.getSize();
public void undo() { target.setSize(Size.SMALL);
if (oldSize != null && target != null) { this.target = target;
Size temp = target.getSize(); }
target.setSize(oldSize);
oldSize = temp; @Override
} public void undo() {
} if (oldSize != null && target != null) {
Size temp = target.getSize();
@Override target.setSize(oldSize);
public void redo() { oldSize = temp;
undo(); }
} }
@Override @Override
public String toString() { public void redo() {
return "Shrink spell"; undo();
} }
}
@Override
public String toString() {
return "Shrink spell";
}
}

View File

@ -1,45 +1,47 @@
package com.iluwatar.command; package com.iluwatar.command;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
public class Wizard extends Target { /**
*
private Deque<Command> undoStack = new LinkedList<>(); * Wizard is the invoker of the commands
private Deque<Command> redoStack = new LinkedList<>(); *
*/
public Wizard() { public class Wizard {
setSize(Size.NORMAL);
setVisibility(Visibility.VISIBLE); private Deque<Command> undoStack = new LinkedList<>();
} private Deque<Command> redoStack = new LinkedList<>();
public void castSpell(Command command, Target target) { public Wizard() {
System.out.println(this + " casts " + command + " at " + target); }
command.execute(target);
undoStack.offerLast(command); public void castSpell(Command command, Target target) {
} System.out.println(this + " casts " + command + " at " + target);
command.execute(target);
public void undoLastSpell() { undoStack.offerLast(command);
if (!undoStack.isEmpty()) { }
Command previousSpell = undoStack.pollLast();
redoStack.offerLast(previousSpell); public void undoLastSpell() {
System.out.println(this + " undoes " + previousSpell); if (!undoStack.isEmpty()) {
previousSpell.undo(); Command previousSpell = undoStack.pollLast();
} redoStack.offerLast(previousSpell);
} System.out.println(this + " undoes " + previousSpell);
previousSpell.undo();
public void redoLastSpell() { }
if (!redoStack.isEmpty()) { }
Command previousSpell = redoStack.pollLast();
undoStack.offerLast(previousSpell); public void redoLastSpell() {
System.out.println(this + " redoes " + previousSpell); if (!redoStack.isEmpty()) {
previousSpell.redo(); Command previousSpell = redoStack.pollLast();
} undoStack.offerLast(previousSpell);
} System.out.println(this + " redoes " + previousSpell);
previousSpell.redo();
@Override }
public String toString() { }
return "Wizard";
} @Override
public String toString() {
} return "Wizard";
}
}