I. Preamble
In front-end development, the Command Pattern (Command Pattern) as a behavioral design pattern, can help us encapsulate the request into an object, so as to achieve decoupling between the calling object and the execution object, easy to extend and modify.
In this article, we will share with you the Command Mode in JavaScript, including the definition of the Command Mode, its core roles, and how to implement the Command Mode in JavaScript.
II. What is command mode
1. Definitions
The Command Pattern is a behavioral design pattern that encapsulates a request into an object, thereby decoupling the object that invokes the operation from the object that performs it. The core idea of the Command Pattern is to encapsulate a request into an object, thus separating the initiator and the executor of the command.
2. Core roles
In the command model, the following core roles are typically included:
Command: Defines a method for executing a command, usually including aexecute
method.
Concrete Command: implements the command interface and is responsible for specific command execution.
Invoker: the object responsible for invoking the command object to execute the request.Receiver: performs the request operation of the command.
The command pattern decouples the initiator and executor of a command by encapsulating the request into an object, and also supports operations such as queuing, logging, and undoing of commands.
3. UML
III. Modalities of realization
In JavaScript, the command pattern can be implemented through a combination of objects and functions. The following is a simple implementation:
1. Defining the command interface
First, define a command interface, usually including a execute
method, for executing commands.
class Command {
execute() {
}
}
2. Defining specific orders
Next, define specific commands that implement the Command
interface and implement specific command logic in the execute
method.
class ConcreteCommand extends Command {
constructor(receiver) {
super();
this.receiver = receiver;
}
execute() {
this.receiver.action();
}
}
3. Defining recipients
The receiver is responsible for performing the actual command operation.
class Receiver {
action() {
}
}
4. Defining the caller
The caller is responsible for invoking the specific command object and executing the command.
class Invoker {
constructor(command) {
this.command = command;
}
executeCommand() {
this.command.execute();
}
}
5. Use of command mode
Finally, specific command objects, receiver objects, and caller objects can be created and commands can be executed using the command pattern.
const receiver = new Receiver();
const concreteCommand = new ConcreteCommand(receiver);
const invoker = new Invoker(concreteCommand);
invoker.executeCommand();
With the above approach, we have implemented a simple example of the command pattern. In this way, we can realize encapsulation of commands, decoupling of callers and receivers, and support operations such as undo and redo of commands. In practical applications, we can also design and extend the command pattern according to specific business scenarios to improve the flexibility of the code.
IV. Simple Player Application
Below is an example analysis of an application scenario where I used the command pattern in a real project:
We have a simple player application where the user can control the play, pause, previous, next, etc. of the music through buttons on the interface. So I chose to implement this player application using command mode as follows:
1. Creating command objects
class PlayCommand {
constructor(player) {
this.player = player;
}
execute() {
this.player.play();
}
}
class PauseCommand {
constructor(player) {
.player = player;
}
execute() {
this.player.pause();
}
}
2. Creating the receiver object (player object)
class Player {
play() {
}
pause() {
}
}
3. Creating the caller object (button object)
class Button {
constructor(command) {
this.command = command;
}
onClick() {
this.command.execute();
}
}
const player = new Player();
const playCommand = new PlayCommand(player);
const pauseCommand = new PauseCommand(player);
const playButton = new Button(playCommand);
const pauseButton = new Button(pauseCommand);
playButton.onClick();
pauseButton.onClick();
In the above example, by using the command pattern, we realize the decoupling between the button and the player, where the button only needs to know the corresponding command object and not the exact logic to execute. This makes it easy to extend new commands and allows better management of user actions.
V. Canvas drawing commands for drawing and undoing
Command pattern has many other application scenarios in front-end development, especially in undo operations, etc. By encapsulating commands into objects, different commands can be managed to bring more flexible and controllable operations.
In Web drawing, Canvas is often dealt with, Canvas has a very large number of APIs, so we forget in use is inevitable, so the use of command mode can be wrapped into different drawing graphics APIs into different command objects, you can realize and specific operations and decoupling between.
Next, let’s analyze how to use the command mode to realize the Canvas drawing and undo functions when you can follow the steps below:
1. Defining the command interface
First define a command interface, including two methods to execute a command ( execute
) and to revoke a command ( undo
).
class Command {
execute() {}
undo() {}
}
2. Writing specific command classes
Write specific command classes that inherit from the command interface, such as the Draw Rectangle command.
class DrawRectCommand extends Command {
constructor(canvas) {
super();
this.canvas = canvas;
}
execute() {
}
undo() {
}
}
class DrawCircleCommand extends Command {
constructor(canvas) {
super();
this.canvas = canvas;
}
execute() {
}
undo() {
}
}
3. Creating command queues
Create a command queue to store executed commands to enable undo operations.
class CommandQueue {
constructor() {
this.commands = [];
}
addCommand(command) {
this.commands.push(command);
}
executeCommands() {
this.commands.forEach((command) => command.execute());
}
undoCommands() {
this.commands.reverse().forEach((command) => command.undo());
}
}
4. Instantiated mapping functions
In a Canvas drawing application, instantiate drawing commands and command queues, and execute or undo commands based on user actions.
const canvas = document.getElementById("canvas");
const drawRectangleCommand = new DrawRectangleCommand(canvas);
const drawCircleCommand = new DrawCircleCommand(canvas);
const commandQueue = new CommandQueue();
commandQueue.addCommand(drawRectangleCommand);
commandQueue.executeCommands();
commandQueue.addCommand(drawCircleCommand);
commandQueue.executeCommands();
commandQueue.undoCommands();
Through the above steps, the command mode can be used to realize the function of drawing and undoing in Canvas. When the user performs a drawing operation, the corresponding command is stored in the command queue, and the user can undo the previous operation at any time, thus realizing the function of drawing and undoing. At the same time, you can also continue to improve more complex drawing operations according to business needs.
The code nuggets demo works as follows:
VI. Advantages and disadvantages
Through the above understanding and learning, we can clearly know the command pattern as a design pattern, the main role is to encapsulate the request into an object in order to decouple the specific implementation, we callers actually do not care about the implementation, only care about the specific command can be. Then it also has some shortcomings, I will specifically analyze the advantages and disadvantages of the command pattern:
1. Advantages
Decoupling the request sender and request receiver: the command pattern can decouple the request sender and request receiver, the sender does not need to know the specific implementation of the receiver, just send the request through the command object.
Easy to extend new commands: Since the command pattern encapsulates each request operation into an object, when new commands need to be added, only a new corresponding command class needs to be added without modifying the existing code.
Support for undo and redo operations: By recording the command history, undo and redo operations of requests can be easily realized, which enhances the flexibility of the system.
Support command queue: you can save command objects in the queue and execute them in a certain order to realize batch processing and other functions.
Easy to implement logging and transaction system: By recording the log of executed commands, you can implement functions such as logging and transaction rollback.
2. Disadvantages
May generate a large number of concrete command classes: If the system has a large number of command operations and each command needs to implement a separate concrete command class, it may lead to an excessive number of classes and increase the complexity of the system.
Increased system complexity: Introducing the command pattern will add extra classes and objects, which may make the system structure more complex and not suitable for simple business scenarios.
Limited scope of application: the command pattern is suitable for scenarios that require decoupling of the request sender and receiver, support for undo redo, etc., and may be too cumbersome for simple request operations.
To summarize, command mode can play its advantages in some specific scenarios, such as when you need to support undo redo, command queue and other functions, command mode is a good choice. However, in simple business scenarios or scenarios with high performance requirements, command mode may not be suitable. In practice, you need to evaluate whether to use command mode according to the specific situation.
VII. Summary
The Command Pattern is a behavioral design pattern whose core idea is to encapsulate a request into an object, thus decoupling the caller and the receiver. The receiver is the object that actually executes the command, while the caller only needs to call the methods of the command object, without knowing the specific implementation details.
The key to implementing the command pattern typically includes the following core roles:
Command: Defines a method for executing a command, usually including aexecute
method.
Concrete Command: implements the command interface and is responsible for specific command execution.
Invoker: the object responsible for invoking the command object to execute the request.Receiver: performs the request operation of the command.
However, the command pattern also has some disadvantages, if our code is not standardized may lead to a large number of specific command classes, increasing the complexity of the system, resulting in the execution of the efficiency of the lower, so in the actual application, you need to evaluate whether to use the command pattern according to the specific situation.