diff --git a/command/README.md b/command/README.md index a3b863b78..e280fc166 100644 --- a/command/README.md +++ b/command/README.md @@ -3,19 +3,21 @@ title: Command category: Behavioral language: en tag: - - Gang of Four + - Gang of Four --- ## Also known as -Action, Transaction +* Action +* Transaction ## Intent -Encapsulate a request as an object, thereby letting you parameterize clients with different -requests, queue or log requests, and support undoable operations. +The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with +queues, requests, and operations. It also allows for the support of 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. @@ -37,155 +39,158 @@ Wikipedia says Here's the sample code with wizard and goblin. Let's start from the `Wizard` class. ```java + @Slf4j public class Wizard { - private final Deque undoStack = new LinkedList<>(); - private final Deque redoStack = new LinkedList<>(); + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); - 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 Wizard() { } - } - public void redoLastSpell() { - if (!redoStack.isEmpty()) { - var previousSpell = redoStack.pollLast(); - undoStack.offerLast(previousSpell); - previousSpell.run(); + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); } - } - @Override - public String toString() { - return "Wizard"; - } + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + 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. ```java + @Slf4j public abstract class Target { - private Size size; + private Size size; - private Visibility visibility; + private Visibility visibility; - public Size getSize() { - return size; - } + public Size getSize() { + return size; + } - public void setSize(Size size) { - this.size = size; - } + public void setSize(Size size) { + this.size = size; + } - public Visibility getVisibility() { - return visibility; - } + public Visibility getVisibility() { + return visibility; + } - public void setVisibility(Visibility visibility) { - this.visibility = visibility; - } + public void setVisibility(Visibility visibility) { + this.visibility = visibility; + } - @Override - public abstract String toString(); + @Override + public abstract String toString(); - public void printStatus() { - LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); - } + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } } public class Goblin extends Target { - public Goblin() { - setSize(Size.NORMAL); - setVisibility(Visibility.VISIBLE); - } + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } - @Override - public String toString() { - return "Goblin"; - } + @Override + public String toString() { + return "Goblin"; + } - public void changeSize() { - var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; - setSize(oldSize); - } + 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); - } + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } } ``` Finally, we have the wizard in the main function casting spells. ```java -public static void main(String[] args) { - var wizard = new Wizard(); - var goblin = new Goblin(); +public static void main(String[]args){ + var wizard=new Wizard(); + var goblin=new Goblin(); - // casts shrink/unshrink spell - wizard.castSpell(goblin::changeSize); + // casts shrink/unshrink spell + wizard.castSpell(goblin::changeSize); - // casts visible/invisible spell - wizard.castSpell(goblin::changeVisibility); + // casts visible/invisible spell + wizard.castSpell(goblin::changeVisibility); - // undo and redo casts - wizard.undoLastSpell(); - wizard.redoLastSpell(); + // undo and redo casts + wizard.undoLastSpell(); + wizard.redoLastSpell(); ``` Here's the whole example in action. ```java -var wizard = new Wizard(); -var goblin = new Goblin(); +var wizard=new Wizard(); + var goblin=new Goblin(); -goblin.printStatus(); -wizard.castSpell(goblin::changeSize); -goblin.printStatus(); + goblin.printStatus(); + wizard.castSpell(goblin::changeSize); + goblin.printStatus(); -wizard.castSpell(goblin::changeVisibility); -goblin.printStatus(); + wizard.castSpell(goblin::changeVisibility); + goblin.printStatus(); -wizard.undoLastSpell(); -goblin.printStatus(); + wizard.undoLastSpell(); + goblin.printStatus(); -wizard.undoLastSpell(); -goblin.printStatus(); + wizard.undoLastSpell(); + goblin.printStatus(); -wizard.redoLastSpell(); -goblin.printStatus(); + wizard.redoLastSpell(); + goblin.printStatus(); -wizard.redoLastSpell(); -goblin.printStatus(); + wizard.redoLastSpell(); + goblin.printStatus(); ``` Here's the program output: ```java -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] +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 @@ -197,40 +202,66 @@ Goblin, [size=small] [visibility=invisible] 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 -called at a later point. Commands are an object-oriented replacement for callbacks. + 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 life -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. + 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 functionality is achieved by traversing this list backward and forward + 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 functionality is achieved by traversing this list backward and forward 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 the disk and re-executing them with -the execute operation. + 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 + 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 data -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 -system with new transactions. + 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, + letting you invoke all transactions the same way. The pattern also makes it easy to extend the + system with new transactions. * Keep a history of requests. * Implement callback functionality. * Implement the undo functionality. ## 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) * [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) * [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 * [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) * [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) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY)