"Command Design Pattern": Elevate Code Control and Flexibility, Simplify User Actions Undo/Redo, and Beyond!

"Command Design Pattern": Elevate Code Control and Flexibility, Simplify User Actions Undo/Redo, and Beyond!

In this article we continue with the series of behavioral design patterns. This is the time for the "Command Pattern" (1.).

The Command Design Pattern is a behavioral design pattern that addresses the problem of decoupling the sender of a request from its receiver. It encapsulates a request as an object, allowing clients to parameterize with different requests, queue or log requests, and support undoable operations. This pattern promotes flexibility, maintainability, and extensibility in software systems, making it easier to manage complex interactions between objects.

The Command Pattern is particularly useful (I would say essential) in a number of situations where it is necessary to manage user actions in a flexible way and simplifying the management of complex interactions.

So let's start immediately with an example that clarifies the situation.

As an example let's consider "user interface toolkits". The UI Toolkits include objects like buttons and menus that carry out a request in response to user input. But the toolkit can't implement the request explicitly in the button or menu, because only applications that use the toolkit know what should be done on which object. As toolkit designers we have no way of knowing the receiver of the request or the operations that will carry it out.

The Command model suggests that GUI objects shouldn't submit these requests directly. Instead, we should extract all the request details, such as the called object , the name of the method and the list of arguments in a separate command class with a single method that triggers this request.

Command objects act as links between various GUIs and business logic objects. From now on, the GUI object does not need to know which business logic object will receive the request and how it will be processed. The GUI object only activates the command, which handles all the details.

The key to this pattern is an abstract Command class, which declares an interface for executing operations. In the simplest form this interface includes an abstract "execute" operation.

Concrete Command subclasses specify a receiver-action pair by storing the receiver as an instance variable and by implementing execute to invoke the request.

The receiver has the knowledge required to carry out the request. Let's see the following example.

The Menus can be implemented easily with Command objects. Each choice in a Menu is an instance of a MenuItem class. An Application class creates the menu and its menu items. The Application class also keeps track of which Document objects the user has opened.

The application configures each MenuItem with an instance of a concrete Command subclass. When the user selects a MenuItem, the MenuItem calls the "execute" method on the related command so performing the operation.

The class diagram shown below clarifies the structure of the solution.

Non è stato fornito nessun testo alternativo per questa immagine

For example, PasteCommand supports pasting text from the clipboard into a Document. The receiver of the PasteCommand is the Document object which is supplied to the instance. The execute operation invokes Paste on the receiving document. Let's see the snippet diagram below.

Non è stato fornito nessun testo alternativo per questa immagine


OpenCommand's execute operation is different: it prompts the user for a document name, creates a corresponding Document object, adds the document to the receiving application, and opens the document. Let's see the snippet diagram below.

Non è stato fornito nessun testo alternativo per questa immagine

In each of these examples, notice how the Command pattern decouples the object that invokes the operation from the one having the knowledge to perform it.

This gives us a lot of flexibility in designing our user interface. An application can provide both a menu and a push button interface to a feature just by making the menu and the push button share an instance of the same concrete Command subclass. We can replace commands dynamically, which would be useful for implementing context-sensitive menus. We can also support command scripting by composing commands into larger ones.

All of this is possible because the object that issues a request only needs to know how to issue it; it doesn't need to know how the request will be carried out.


Now let's see if there are real-world applications of the command pattern. In a diner setting, the "order" can be an excellent real-world example of the Command Design Pattern. Let's explore how the Command pattern can be applied to manage the process of generating a customer's order, managing dish prep, and managing the bill in a diner.

image taken from "refactoring.guru".

These are the components of the Command Pattern for diner management:

  1. Command Interface: The Command interface represents the common operations that any command should support, in our case "execute".
  2. Concrete Command: Concrete Command classes implement the Command interface and encapsulate specific actions related to the customer's order, such as adding an item to the order, removing an item from the order, etc..
  3. Receiver: The Receiver is the object that knows how to execute the command. In this case, the Receiver could be the Chef who has to prepare the dish and the Cashier who has to calculate the total amount.
  4. Invoker: The Invoker is responsible for storing and executing the commands. In the diner setting, the Invoker could be the Waiter, who takes orders from customers, passes the commands to the chef and to the cashier.

Below we see an example of a sequence diagram relating to the implementation of the command pattern for managing the customer's order in a diner:

The waiter or waitress takes an order or command from a customer and encapsulates that order by writing it on the check. The order is then queued for the kitchen. Note that the chefs and the cashier are the receivers, the chef will prepare the dishes and the cashier will print the check.

Now let's see the implementation of the code in java.

In the example we have considered the presence of two chef, "Stefano e "Luisa".

As you can see we have also considered the preferences in order management such as "rare" for the steak and "without vinegar" for the salad.

This information will be used by the chef when preparing the dishes of the order.

I also considered having two chef in the kitchen. The waiter taking the order is the one who decides which chef the order is given to.


As we can see from the console picture the program seems to work fine.


Below we can see the "MenuItem" data class and the "Command" interface on which the pattern is based and then we also see two concrete commands: "AddItemCommand" e "RemoveItemCommand".


Below you can see the Invoker class of the "Waiter" and the two Receiver classes "Chasier" and "Chef":


At this point, after we saw two examples of command pattern implementations, the "toolkit menu" and the "dinner order", the time has come to examine the general structure of the command pattern in detail.

Let's start with the static structure : the class diagram.

The class diagram for the command pattern includes the following components:

  • Command: This is the central component that declares an interface or an abstract class with an execute() method, defining the contract for executing the command.
  • Concrete Command: Concrete Command classes implement the Command interface and encapsulate specific actions and their corresponding receiver objects. Concrete Commands implement various kinds of requests. A concrete command isn’t supposed to perform the work on its own, but rather to pass the call to one of the business logic objects. Parameters required to execute a method on a receiving object can be declared as fields in the concrete command. We can make command objects immutable by only allowing the initialization of these fields via the constructor.
  • Client: The client creates and sets up the command object. The client must pass all of the request parameters, including a receiver instance, into the command’s constructor. After that, the resulting command may be associated with one or multiple senders.
  • Invoker: The invoker stores and executes commands, abstracting the client from the details of command execution. The invoker is responsible for initiating requests. This class must have a field for storing a reference to a command object. The invoker calls the execute method of that command instead of sending the request directly to the receiver. Note that the sender isn’t responsible for creating the command object. Usually, it gets a pre-created command from the client via the constructor.
  • Receiver: The receiver knows how to perform the operation associated with a command. It is the target object that executes the request.


And now let's see the dynamic behavior of the pattern.

The dynamic object collaboration is shown in the sequence diagram below where we could see the interactions between the pattern components.

  • In this sample scenario the client creates the invoker and receiver objects. Then it creates a ConcreteCommand object and passes it to the invoker.
  • The interaction starts with the Invoker object that calls execute() method on its Command object.
  • The ConcreteCommand object calls operation() method on a Receiver object.


Well, now that we've seen the structure of the pattern in detail, let's look at a couple of scenarios where the pattern command can be very useful.

Imagine we have a universal remote control that can operate various electronic devices like TVs, DVD players, and sound systems. The remote control has multiple buttons, each representing a different function (e.g., turn on, turn off, increase volume, decrease volume, change the channel, etc.). The Command Pattern helps in implementing this functionality effectively.

The components in action are always the same:

  1. Invoker: The remote control itself is the invoker. It doesn't know the specifics of how each device or function works; it just knows how to execute a command.
  2. Commands: Each button on the remote control represents a command. These commands encapsulate specific actions like turning on a TV or increasing the volume of a sound system.
  3. Receivers: The electronic devices (TV, DVD player, sound system) are the receivers. They know how to perform the actual action when a command is executed on them.

I wrote a conceptual example with a TV object and a remote control object, I wrote it in C++ language, and below we see the source code.


In this example, we've created a simple TV receiver and two command objects: TurnOnCommand and TurnOffCommand. The RemoteControl acts as the invoker. When you press a button on the remote control, it sets the appropriate command and then executes it, which triggers the corresponding action on the TV.


And now let's see another scenario of applying the pattern command

In concurrent programming, the Command Design Pattern can be useful for managing multi-threading scenarios and task queues. Commands can represent tasks or operations that need to be executed concurrently. The invoker maintains a queue of commands, and multiple threads can process these commands concurrently, allowing for efficient use of system resources.

The "Dining Philosophers", a classic problem involving concurrency and shared resources.


The components in action are always the same:

  1. Invoker: The invoker in this context can be a task scheduler or a thread pool manager. It's responsible for executing the commands (tasks) in the correct order and managing the threads.
  2. Commands: Each command represents a specific task or operation that you want to perform concurrently. These commands encapsulate the work to be done.
  3. Receiver: The receiver is the object that performs the actual task when the command is executed. In this example, the receiver could be a worker thread.


I wrote a conceptual example where we have a task queue that can execute commands (tasks) concurrently using a thread pool. I wrote it in Kotlin language.

In this example, there is a TaskQueue acting as the invoker that manages a thread pool with two worker threads. There are also two concrete commands, Task1 and Task2, each representing a specific task to be executed concurrently. When we add a task to the TaskQueue, it is executed by one of the worker threads from the thread pool. The WorkerThread class represents the receiver, which executes the actual task.


Before finishing this analysis on the command pattern, let's examine the relationship between the Command Design Pattern and each of the SOLID principles (.2,.3.4.5.6):

  1. Single Responsibility Principle (SRP) (2.): The SRP states that a class should have only one reason to change, meaning it should have a single responsibility. The Command Design Pattern helps adhere to this principle by encapsulating each command in a separate class. Each command is responsible for performing a specific action, and any changes related to that action are confined within the respective command class. This isolation promotes maintainability as the changes to one command do not affect the others.
  2. Open/Closed Principle (OCP) (3.): The OCP suggests that software entities (classes, modules, functions) should be open for extension but closed for modification. The Command Design Pattern follows this principle by allowing the addition of new commands without modifying the existing client code. We can introduce new Concrete Command classes without changing the client, invoker, or receiver classes, making the system more extensible.
  3. Liskov Substitution Principle (LSP) (4.): The LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. The Command Design Pattern doesn't directly influence this principle because it primarily deals with command execution and request encapsulation rather than class hierarchy. However, adhering to SRP and OCP through the pattern indirectly supports the goals of the LSP.
  4. Interface Segregation Principle (ISP) (5.): The ISP advises that a class should not be forced to implement interfaces it doesn't use. In the context of the Command Design Pattern, this principle isn't explicitly tied to the pattern itself. However, when implementing the Command pattern, we define an interface or abstract class for commands. This interface should only contain the necessary methods (e.g., execute()), ensuring that classes that implement it have a specific purpose without being bloated with irrelevant methods.
  5. Dependency Inversion Principle (DIP) (6.): The DIP suggests that high-level modules should not depend on low-level modules; both should depend on abstractions. The Command Design Pattern can adhere to this principle by introducing abstractions (interfaces) for commands. The client and invoker classes depend on the Command interface (abstraction) rather than concrete command classes (implementation). This allows for a flexible, interchangeable command setup, where different commands can be easily plugged into the system without changing the client or invoker code.


That's it for the "Command Pattern".


I remind you my newsletter?"Sw Design & Clean Architecture"? :?https://lnkd.in/eUzYBuEX?where you can find my previous articles and where you can register, if you have not already done, so you will be notified when I publish new articles.

?

Thanks for reading my article, and I hope you have found the topic useful,

Feel free to leave any feedback.

Your feedback is very appreciated.

Thanks again.

Stefano

?


References:

1.?Gamma, Helm, Johnson, Vlissides, “Design Patterns”, Addison Wesley (2° Edition October 2002). pp 199-207.

2.?S. Santilli: "https://www.dhirubhai.net/pulse/single-responsibility-principle-stefano-santilli/".?

3.?S. Santilli: "https://www.dhirubhai.net/pulse/open-closed-principle-stefano-santilli/".?

4.?S. Santilli: "https://www.dhirubhai.net/pulse/squarerectangle-problem-stefano-santilli/".?

5.?S. Santilli: "https://www.dhirubhai.net/pulse/interface-segregation-principle-isp-stefano-santilli/".

6.?S. Santilli: "https://www.dhirubhai.net/pulse/dependency-inversion-principle-stefano-santilli/".?

Anas AlGaferee

IT Support Specialist, Designer, Developer

1 年

Dear Stefano Santilli ????, I would like to express my deep gratitude and heartfelt thanks for the outstanding article , I found it to be truly inspiring and immensely valuable. I greatly appreciated your writing style and the organization of the information. You provided clear examples and illustrative code snippets that made it easy to comprehend complex concepts. Additionally, your ability to highlight practical use cases and real-world scenarios for the design pattern helped solidify my understanding of the topic.

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了