mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-03 05:20:09 +00:00
docs: update Command documentation
This commit is contained in:
1 parent
8471c93035
commit
30ac97fe59
1 file changed
+143
-112
+143
-112
@@ -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)
|
||||||
Reference in new issue
Block a user