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:
Rakesh Venkatesh 2020-09-30 17:56:12 +02:00
parent 1f4a412e70
commit 4ff196ce35
11 changed files with 206 additions and 434 deletions

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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();

View File

@ -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();
}

View File

@ -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);
}
} }

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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();
} }
} }

View File

@ -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();