Refactor the command pattern to use lambda functions
We can leverage the lambda expressins of Java 8 onwards to implement command design pattern instead of traditional non functional way
This commit is contained in:
parent
1f4a412e70
commit
4ff196ce35
@ -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
|
||||||
|
@ -32,10 +32,10 @@ package com.iluwatar.command;
|
|||||||
* 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. 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
|
* 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
|
* does bookkeeping about the command execution. The invoker does not know anything about a is
|
||||||
* command, it knows only about command interface. Both an invoker object and several command
|
* executed. Both an invoker object and several command
|
||||||
* objects are held by a client object (app). The client decides which commands to execute at which
|
* 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.
|
* points. To execute a command, it passes a reference of 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 +54,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);
|
||||||
@ -38,4 +43,20 @@ public class Goblin extends Target {
|
|||||||
return "Goblin";
|
return "Goblin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* changeSize.
|
||||||
|
*/
|
||||||
|
public void changeSize() {
|
||||||
|
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
|
||||||
|
setSize(oldSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* changeVisibility.
|
||||||
|
*/
|
||||||
|
public void changeVisibility() {
|
||||||
|
var visible = getVisibility() == Visibility.INVISIBLE
|
||||||
|
? Visibility.VISIBLE : Visibility.INVISIBLE;
|
||||||
|
setVisibility(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user