docs: update Command documentation

This commit is contained in:
Ilkka Seppälä committed 2024-03-30 14:44:22 +02:00
1 parent 8471c93035
commit 30ac97fe59
1 file changed
+143 -112
+143 -112
View File
@@ -3,19 +3,21 @@ title: Command
category: Behavioral category: Behavioral
language: en language: en
tag: tag:
- Gang of Four - Gang of Four
--- ---
## Also known as ## Also known as
Action, Transaction * Action
* Transaction
## Intent ## Intent
Encapsulate a request as an object, thereby letting you parameterize clients with different The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with
requests, queue or log requests, and support undoable operations. queues, requests, and operations. It also allows for the support of 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.
@@ -37,155 +39,158 @@ Wikipedia says
Here's the sample code with wizard and goblin. Let's start from the `Wizard` class. Here's the sample code with wizard and goblin. Let's start from the `Wizard` class.
```java ```java
@Slf4j @Slf4j
public class Wizard { public class Wizard {
private final Deque<Runnable> undoStack = new LinkedList<>(); private final Deque<Runnable> undoStack = new LinkedList<>();
private final Deque<Runnable> redoStack = new LinkedList<>(); private final Deque<Runnable> redoStack = new LinkedList<>();
public Wizard() {} public Wizard() {
public void castSpell(Runnable runnable) {
runnable.run();
undoStack.offerLast(runnable);
}
public void undoLastSpell() {
if (!undoStack.isEmpty()) {
var previousSpell = undoStack.pollLast();
redoStack.offerLast(previousSpell);
previousSpell.run();
} }
}
public void redoLastSpell() { public void castSpell(Runnable runnable) {
if (!redoStack.isEmpty()) { runnable.run();
var previousSpell = redoStack.pollLast(); undoStack.offerLast(runnable);
undoStack.offerLast(previousSpell);
previousSpell.run();
} }
}
@Override public void undoLastSpell() {
public String toString() { if (!undoStack.isEmpty()) {
return "Wizard"; var previousSpell = undoStack.pollLast();
} redoStack.offerLast(previousSpell);
previousSpell.run();
}
}
public void redoLastSpell() {
if (!redoStack.isEmpty()) {
var previousSpell = redoStack.pollLast();
undoStack.offerLast(previousSpell);
previousSpell.run();
}
}
@Override
public String toString() {
return "Wizard";
}
} }
``` ```
Next, we have the goblin who's the target of the spells. Next, we have the goblin who's the target of the spells.
```java ```java
@Slf4j @Slf4j
public abstract class Target { public abstract class Target {
private Size size; private Size size;
private Visibility visibility; private Visibility visibility;
public Size getSize() { public Size getSize() {
return size; return size;
} }
public void setSize(Size size) { public void setSize(Size size) {
this.size = size; this.size = size;
} }
public Visibility getVisibility() { public Visibility getVisibility() {
return visibility; return visibility;
} }
public void setVisibility(Visibility visibility) { public void setVisibility(Visibility visibility) {
this.visibility = visibility; this.visibility = visibility;
} }
@Override @Override
public abstract String toString(); public abstract String toString();
public void printStatus() { public void printStatus() {
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
} }
} }
public class Goblin extends Target { public class Goblin extends Target {
public Goblin() { public Goblin() {
setSize(Size.NORMAL); setSize(Size.NORMAL);
setVisibility(Visibility.VISIBLE); setVisibility(Visibility.VISIBLE);
} }
@Override @Override
public String toString() { public String toString() {
return "Goblin"; return "Goblin";
} }
public void changeSize() { public void changeSize() {
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
setSize(oldSize); setSize(oldSize);
} }
public void changeVisibility() { public void changeVisibility() {
var visible = getVisibility() == Visibility.INVISIBLE var visible = getVisibility() == Visibility.INVISIBLE
? Visibility.VISIBLE : Visibility.INVISIBLE; ? Visibility.VISIBLE : Visibility.INVISIBLE;
setVisibility(visible); setVisibility(visible);
} }
} }
``` ```
Finally, we have the wizard in the main function casting spells. Finally, we have the wizard in the main function casting spells.
```java ```java
public static void main(String[] args) { public static void main(String[]args){
var wizard = new Wizard(); var wizard=new Wizard();
var goblin = new Goblin(); var goblin=new Goblin();
// casts shrink/unshrink spell // casts shrink/unshrink spell
wizard.castSpell(goblin::changeSize); wizard.castSpell(goblin::changeSize);
// casts visible/invisible spell // casts visible/invisible spell
wizard.castSpell(goblin::changeVisibility); wizard.castSpell(goblin::changeVisibility);
// undo and redo casts // undo and redo casts
wizard.undoLastSpell(); wizard.undoLastSpell();
wizard.redoLastSpell(); 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(goblin::changeSize); wizard.castSpell(goblin::changeSize);
goblin.printStatus(); goblin.printStatus();
wizard.castSpell(goblin::changeVisibility); wizard.castSpell(goblin::changeVisibility);
goblin.printStatus(); goblin.printStatus();
wizard.undoLastSpell(); wizard.undoLastSpell();
goblin.printStatus(); goblin.printStatus();
wizard.undoLastSpell(); wizard.undoLastSpell();
goblin.printStatus(); goblin.printStatus();
wizard.redoLastSpell(); wizard.redoLastSpell();
goblin.printStatus(); goblin.printStatus();
wizard.redoLastSpell(); wizard.redoLastSpell();
goblin.printStatus(); goblin.printStatus();
``` ```
Here's the program output: Here's the program output:
```java ```java
Goblin, [size=normal] [visibility=visible] Goblin,[size=normal][visibility=visible]
Goblin, [size=small] [visibility=visible] Goblin,[size=small][visibility=visible]
Goblin, [size=small] [visibility=invisible] Goblin,[size=small][visibility=invisible]
Goblin, [size=small] [visibility=visible] Goblin,[size=small][visibility=visible]
Goblin, [size=normal] [visibility=visible] Goblin,[size=normal][visibility=visible]
Goblin, [size=small] [visibility=visible] Goblin,[size=small][visibility=visible]
Goblin, [size=small] [visibility=invisible] Goblin,[size=small][visibility=invisible]
``` ```
## Class diagram ## Class diagram
@@ -197,40 +202,66 @@ Goblin, [size=small] [visibility=invisible]
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 life * Specify, queue, and execute requests at different times. A Command object can have a life
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 functionality is achieved by traversing this list backward and forward Unlimited-level undo and redo functionality is achieved by traversing this list backward and forward
calling un-execute and execute, respectively. calling 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 the disk and re-executing them with Recovering from a crash involves reloading logged commands from the 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 data common in information systems that support transactions. A transaction encapsulates a set of data
changes. The Command pattern offers a way to model transactions. Commands have a common interface, changes. 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.
* Keep a history of requests. * Keep a history of requests.
* Implement callback functionality. * Implement callback functionality.
* Implement the undo functionality. * Implement the undo functionality.
## Known uses ## Known uses
* GUI Buttons and menu items in desktop applications.
* Operations in database systems and transactional systems that support rollback.
* Macro recording in applications like text editors and spreadsheets.
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) * [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java)
* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) * [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki)
* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) * [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html)
## Consequences
Benefits:
* Decouples the object that invokes the operation from the one that knows how to perform it.
* It's easy to add new Commands, because you don't have to change existing classes.
* You can assemble a set of commands into a composite command.
Trade-offs:
* Increases the number of classes for each individual command.
* Can complicate the design by adding multiple layers between senders and receivers.
## Related Patterns
* [Composite](https://java-design-patterns.com/patterns/composite/): Commands can be composed using the Composite
pattern
to create macro commands.
* [Memento](https://java-design-patterns.com/patterns/memento/): Can be used for implementing undo mechanisms.
* [Observer](https://java-design-patterns.com/patterns/observer/): The pattern can be observed for changes that trigger
commands.
## Credits ## Credits
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) * [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) * [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) * [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) * [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY)