Flutter Design Patterns: 12 — Command
An overview of the Command design pattern and its implementation in Dart and Flutter
Previously in the series, I have analysed and implemented one of the most popular and useful creational design patterns — Abstract Factory. This time, I would like to introduce an OOP design pattern which belongs to the category of behavioural design patterns — the Command.
Table of Contents
- What is the Command design pattern?
- Other articles in this series
- Your contribution
What is the Command design pattern?
Command, also known as Action or Transaction, is one of the behavioural design patterns which intention is described in the GoF book:
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
That is, by encapsulating the request as an object, the client is completely decoupled from any of the details of how that command is implemented or how it will get executed — the client doesn’t have to care about any dependencies.
There are multiple components of which the Command design pattern is consisted of. Usually, Command objects serve as links between various UI and business logic objects. In this case, the command object could be bound to the UI element at runtime by the Client and that particular UI component, called Sender, invokes the request. The Sender triggers the assigned Command instead of sending the request directly to the Receiver - a simple or complex object which contains the actual logic for the request to be fulfilled. A specific Command passes the call to the receiver which does the actual work. As a result, commands become a convenient middle layer that reduces coupling between the UI and business logic layers.
Let’s move to the analysis and implementation parts to understand the details about this pattern and learn how to implement it!
The general structure of the Command design pattern looks like this:
- Command — declares an interface for executing an operation;
- Concrete Commands (Command1/Command2) — implement various kinds of requests by invoking the corresponding operation(s) on Receiver;
- Invoker — the sender class which triggers the Command instead of sending the request directly to the Receiver;
- Receiver — knows how to perform the operations associated with carrying out a request. Any class may serve as a Receiver;
- Client — creates a Concrete Command object and sets its Receiver.
The Command design pattern could be used when you want to parameterize objects by an action to perform. That is, the operation (command) is extracted to a separate class which object could be passed as a method argument, stored inside another object or the linked command could be switched at runtime.
Furthermore, the Command design pattern is useful when you want to queue operations, schedule their execution, or execute them remotely. Since the command itself is just a simple class, its object (as any other object) could be serialized, stored, e.g. in the database or a text file and later restored as the initial command and executed. This is useful when you want to schedule a specific task which should be executed at a particular time, or on a recurring schedule.
Also, one of the most popular use-case for the Command is to use it for creating reversible operations. To be able to revert operations, you need to implement the history of performed operations. The command history is a stack that contains all executed command objects along with related backups of the application’s state.
Finally, the Command design pattern helps you write cleaner and reusable code. By using this pattern, you propagate the Single Responsibility Principle (the operation logic is decoupled from the component which performs that particular operation) and the Open/Closed Principle (introducing new commands to the application does not require to change the existing code).
To show the Command design pattern in action, we will implement a fake graphics editor. The editor itself is super-ultra-mega-duper simplified (well, Photoshop should have started from something, right?) and provides the following functionality:
- There is only one shape visible on the screen which cannot be removed, but its parameters (colour, height and width) could be adjusted using buttons;
- Change the shape’s colour to a random one;
- Change the shape’s height to a random value between 50 and 150;
- Change the shape’s width to a random value between 50 and 150;
- All of the operations could be undone using the Undo button.
And that is basically it. I know, as a graphics editor, this one sounds terrible, but it would be more than enough to demonstrate the purpose of the Command design pattern.
The main idea behind the implementation of the mentioned graphics editor is to separate the actual business logic of the button from its representation. To achieve this, each operation (request) of the button is encapsulated in a separate class object. Also, by using the Command design pattern, the implementation of undo operation is possible — since every command is encapsulated in a separate class, it is easy to store an objects’ list of already executed commands as well as undoing the last operation by taking the last command from the list and calling the undo() method on it.
Let’s check the class diagram first and then investigate each class/component to see how the Command design pattern could help us building such a graphics editor.
The class diagram below shows the implementation of the Command design pattern:
Command is an abstract class which is used as an interface for all the specific commands:
- execute() — an abstract method which executes the command;
- getTitle() — an abstract method which returns the command’s title. Used in command history UI;
- undo() — an abstract method which undoes the command and returns the receiver to the previous state.
ChangeColorCommand, ChangeHeightCommand and ChangeWidthCommand are concrete command classes which implement the abstract class Command and its methods.
Shape is a receiver class which stores multiple properties defining the shape presented in UI: color, height and width.
CommandHistory is a simple class which stores a list of already executed commands (commandList) and provides methods to add a new command to the command history list (add()) and undo the last command from that list (undo()).
CommandExample initializes and contains CommandHistory, Shape objects. Also, this component contains multiple PlatformButton widgets which have a specific implementation of Command assigned to each of them. When the button is pressed, the command is executed and added to the command history list stored in CommandHistory object.
A simple class to store information about the shape: its color, height and width. Also, this class contains a named constructor to create a shape object with pre-defined initial values.
An interface which defines methods to be implemented by the specific command classes. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.
ChangeColorCommand — a specific implementation of the command which changes the color of the Shape object.
ChangeHeightCommand — a specific implementation of the command which changes the height of the Shape object.
ChangeWidthCommand — a specific implementation of the command which changes the width of the Shape object.
A simple class which stores a list of already executed commands. Also, this class provides isEmpty and commandHistoryList getter methods to return true if the command history list is empty and return a list of command names stored in the command history respectively. A new command could be added to the command history list via the add() method and the last command could be undone using the undo() method (if the command history list is not empty).
First of all, a markdown file is prepared and provided as a pattern’s description:
CommandExample contains CommandHistory and Shape objects. Also, this widget contains several PlatformButton components, each of which uses a specific function executing a concrete command. After the command’s execution, it is added to the command history list stored in the CommandHistory object. If the command history is not empty, the Undo button is enabled and the last command could be undone.
The client code (UI elements, command history, etc.) isn’t coupled to concrete command classes because it works with commands via the command interface. This approach allows introducing new commands into the application without breaking any existing code.
As you can see in the example, by triggering a specific command, its object is created, executed and added to the command history list. Hence, it is possible to undo the command even though it was executed several steps before — that’s one of the advantages of using the Command design pattern.
All of the code changes for the Command design pattern and its example implementation could be found here.
Other articles in this series
👏 Press the clap button below to show your support and motivate me to write better!
💬 Leave a response to this article by providing your insights, comments or wishes for the series.
📢 Share this article with your friends, colleagues in social media.
➕ Follow me on Medium.
⭐ Star the Github repository.