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:
@ -14,14 +14,14 @@ Action, Transaction
|
||||
|
||||
## 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.
|
||||
|
||||
## Explanation
|
||||
Real world example
|
||||
|
||||
> 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
|
||||
> 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 spells one by one. Each spell here is a command object that can be undone.
|
||||
|
||||
In plain words
|
||||
@ -30,8 +30,8 @@ In plain words
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> 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
|
||||
> 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
|
||||
> a later time.
|
||||
|
||||
**Programmatic Example**
|
||||
@ -48,18 +48,16 @@ public class Wizard {
|
||||
|
||||
public Wizard() {}
|
||||
|
||||
public void castSpell(Command command, Target target) {
|
||||
LOGGER.info("{} casts {} at {}", this, command, target);
|
||||
command.execute(target);
|
||||
undoStack.offerLast(command);
|
||||
public void castSpell(Runnable runnable) {
|
||||
runnable.run();
|
||||
undoStack.offerLast(runnable);
|
||||
}
|
||||
|
||||
public void undoLastSpell() {
|
||||
if (!undoStack.isEmpty()) {
|
||||
var previousSpell = undoStack.pollLast();
|
||||
redoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
||||
previousSpell.undo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,8 +65,7 @@ public class Wizard {
|
||||
if (!redoStack.isEmpty()) {
|
||||
var previousSpell = redoStack.pollLast();
|
||||
undoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
||||
previousSpell.redo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,84 +76,7 @@ public class Wizard {
|
||||
}
|
||||
```
|
||||
|
||||
Next we present the spell hierarchy.
|
||||
|
||||
```java
|
||||
public interface Command {
|
||||
|
||||
void execute(Target target);
|
||||
|
||||
void undo();
|
||||
|
||||
void redo();
|
||||
|
||||
String toString();
|
||||
}
|
||||
|
||||
public class InvisibilitySpell implements Command {
|
||||
|
||||
private Target target;
|
||||
|
||||
@Override
|
||||
public void execute(Target target) {
|
||||
target.setVisibility(Visibility.INVISIBLE);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
if (target != null) {
|
||||
target.setVisibility(Visibility.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redo() {
|
||||
if (target != null) {
|
||||
target.setVisibility(Visibility.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Invisibility spell";
|
||||
}
|
||||
}
|
||||
|
||||
public class ShrinkSpell implements Command {
|
||||
|
||||
private Size oldSize;
|
||||
private Target target;
|
||||
|
||||
@Override
|
||||
public void execute(Target target) {
|
||||
oldSize = target.getSize();
|
||||
target.setSize(Size.SMALL);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
if (oldSize != null && target != null) {
|
||||
var temp = target.getSize();
|
||||
target.setSize(oldSize);
|
||||
oldSize = temp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redo() {
|
||||
undo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shrink spell";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we have the goblin who's the target of the spells.
|
||||
Next, we have the goblin who's the target of the spells.
|
||||
|
||||
```java
|
||||
public abstract class Target {
|
||||
@ -203,33 +123,73 @@ public class Goblin extends Target {
|
||||
return "Goblin";
|
||||
}
|
||||
|
||||
public void changeSize() {
|
||||
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
|
||||
setSize(oldSize);
|
||||
}
|
||||
|
||||
public void changeVisibility() {
|
||||
var visible = getVisibility() == Visibility.INVISIBLE
|
||||
? Visibility.VISIBLE : Visibility.INVISIBLE;
|
||||
setVisibility(visible);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally we have the wizard in main function who casts spell
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
var wizard = new Wizard();
|
||||
var goblin = new Goblin();
|
||||
|
||||
// casts shrink/unshrink spell
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
|
||||
// casts visible/invisible spell
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
|
||||
// undo and redo casts
|
||||
wizard.undoLastSpell();
|
||||
wizard.redoLastSpell();
|
||||
```
|
||||
|
||||
Here's the whole example in action.
|
||||
|
||||
```java
|
||||
var wizard = new Wizard();
|
||||
var goblin = new Goblin();
|
||||
|
||||
goblin.printStatus();
|
||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
goblin.printStatus();
|
||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
||||
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
```
|
||||
|
||||
Here's the program output:
|
||||
|
||||
```java
|
||||
// Goblin, [size=normal] [visibility=visible]
|
||||
// Wizard casts Shrink spell at Goblin
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
// Wizard casts Invisibility spell at Goblin
|
||||
// Goblin, [size=small] [visibility=invisible]
|
||||
// Wizard undoes Invisibility spell
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=normal] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=invisible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=normal] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=invisible]
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -240,26 +200,26 @@ Here's the program output:
|
||||
|
||||
Use the Command pattern when you want to:
|
||||
|
||||
* 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
|
||||
* 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
|
||||
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
|
||||
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
|
||||
* 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
|
||||
space-independent way, then you can transfer a command object for the request to a different process
|
||||
and fulfill the request there.
|
||||
* 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
|
||||
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
|
||||
* 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
|
||||
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
|
||||
un-execute and execute, respectively.
|
||||
* 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.
|
||||
Recovering from a crash involves reloading logged commands from disk and re-executing them with
|
||||
* 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.
|
||||
Recovering from a crash involves reloading logged commands from disk and re-executing them with
|
||||
the execute operation.
|
||||
* 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
|
||||
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
|
||||
* 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
|
||||
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
|
||||
system with new transactions.
|
||||
|
||||
## Typical Use Case
|
||||
|
Reference in New Issue
Block a user