This is the sixth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Command mode is one of the simplest and most elegant modes. A command in command mode refers to an instruction that does something specific.
The purpose of command mode
The most common application of the command pattern is when you need to send a request to some object without knowing who the recipient of the request is or what the requested operation is. At this point, you want to design the program in a loosely-coupled way, so that the request sender and the request receiver can decouple from each other.
An example of command mode – menu program
Imagine writing a user interface program that has at least dozens of buttons on it. Because of the complexity of the project, we assigned one person to draw the buttons, and another person to write the actions of clicking the buttons, which were encapsulated in objects.
So, to the programmer who draws the button, he has no idea what the button will do in the future, only that something will happen when he clicks it. Once the button is drawn, how do you bind the onclick event to it?
The obvious reason for using the command pattern here is that after the button is clicked, the request must be sent to some object responsible for the specific behavior, which is the recipient of the request. But it is not known what the recipient is or what exactly the recipient will do. This is where you need the help of a command object to decouple the button from the object responsible for the specific behavior.
The theme of design patterns is always to separate things that do not change from things that change. There are some things that happen after you press the button that don’t change, but what exactly happens is variable. With the help of the Command object, this association can easily be changed in the future, so the button’s behavior can be changed again in the future.
First draw the button in the page:
<button id="button1"></button>
<button id="button2"></button>
<button id="button3"></button>
<script>
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
</script>
Copy the code
Next, define the setCommand function, which installs commands on the button. We can be sure that clicking a button will execute a command, and the action of executing a command is defined by calling the execute() method of the command object. The programmer who draws the button only needs to reserve the interface to install the command. The command object knows how to communicate with the right object:
var setCommand = function(button, command) {
button.onclick = function() {
command.execute()
}
}
Copy the code
Click the button to refresh the menu interface, add submenus, and delete submenus. These functions are distributed in the MenuBar and SubMenu objects:
var MenuBar = {
refresh: function() {
console.log('Refresh menu directory')}}var SubMenu = {
add: function() {
console.log('Add submenu')},del: function(){
console.log('Delete submenu'); }}Copy the code
Before we can make buttons useful, we need to wrap these behaviors in a command class:
var RefreshMenuBarCommand = function (receiver) {
this.receiver = receiver;
};
RefreshMenuBarCommand.prototype.execute = function () {
this.receiver.refresh();
};
var AddSubMenuCommand = function (receiver) {
this.receiver = receiver;
};
AddSubMenuCommand.prototype.execute = function () {
this.receiver.add();
};
var DelSubMenuCommand = function (receiver) {
this.receiver = receiver;
};
DelSubMenuCommand.prototype.execute = function () {
this.receiver.del();
};
Copy the code
Finally, pass the receiver of the command into the command object and install the command object on the button:
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand(button1, refreshMenuBarCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);
Copy the code
The above is a very simple example of the command pattern, but you can see how we decouple the request sender from the request receiver.
Command mode in JavaScript
From the above, the so-called command mode looks like giving the name execute to a method on an object. Introducing a Command object and a Receiver to write two roles out of thin air is just complicating a simple thing, even if the same function can be achieved in a few lines of code without any pattern:
var bindClick = function (button, func) {
button.onclick = func;
};
var MenuBar = {
refresh: function () {
console.log("Refresh menu interface"); }};var SubMenu = {
add: function () {
console.log("Add submenu");
},
del: function () {
console.log("Delete submenu"); }}; bindClick(button1, MenuBar.refresh); bindClick(button2, SubMenu.add); bindClick(button3, SubMenu.del);Copy the code
This is true; the sample code in the previous section emulates the command pattern implementation of a traditional object-oriented language. The command pattern encapsulates a procedural request call in the Execute method of the Command object. By encapsulating the method call, you can wrap the block into shape. Command objects can be passed around, so customers don’t need to care about how things work when commands are invoked.
The origin of the command pattern is an object-oriented alternative to the callback function.
In JavaScript, blocks don’t have to be wrapped in a command-execute method. They can also be wrapped in a normal function. Even if we still need to request the “receiver,” it’s not necessarily in an object-oriented way; closures can do the same thing.
var setCommand = function (button, func) {
button.onclick = function () {
func();
};
};
var MenuBar = {
refresh: function () {
console.log("Refresh menu interface"); }};var RefreshMenuBarCommand = function (receiver) {
return function () {
receiver.refresh();
};
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
Copy the code
Of course, if you want to make it more explicit that the command mode is currently being used, or that in addition to executing commands, you might also want to provide operations such as undo commands in the future. Instead of calling the execute function, we’d better call the execute method:
var RefreshMenuBarCommand = function (receiver) {
return {
execute: function () { receiver.refresh(); }}; };var setCommand = function (button, command) {
button.onclick = function () {
command.execute();
};
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
Copy the code
Cancel the order
The function of command mode is not only to encapsulate the operation block, but also to conveniently add undo operations to the command object.
One of the Animate classes I wrote before writes an animation that moves a ball on a page to a certain horizontal position. The page now has an input text box and a button. You can enter some numbers in the text box to indicate the horizontal position of the ball. The ball begins to move immediately after the user clicks the button.
<div
id="ball"
style="position: absolute; background: #000; width: 50px; height: 50px"
></div>Enter the position of the ball after it moves:<input id="pos" />
<button id="moveBtn">Began to move</button>
<script>
var ball = document.getElementById("ball");
var pos = document.getElementById("pos");
var moveBtn = document.getElementById("moveBtn");
moveBtn.onclick = function () {
var animate = new Animate(ball);
animate.start("left", pos.value, 1000."strongEaseOut");
};
</script>
Copy the code
If you type 200 into the text box and then click the moveBtn button, you can see that the ball moves smoothly to a horizontal position of 200px. Now we need a way to get the ball back to where it was before we started moving. You can also type -200 again in the text box and hit the moveBtn button, which is one way to do it, but it’s awkward. It’s a good idea to have an undo button on the page. When you click undo, the ball will go back to where it was last time.
Before adding an undo button to the page, change the current code to a command mode:
var ball = document.getElementById("ball");
var pos = document.getElementById("pos");
var moveBtn = document.getElementById("moveBtn");
var MoveCommand = function (receiver, pos) {
this.receiver = receiver;
this.pos = pos;
};
MoveCommand.prototype.execute = function () {
this.receiver.start("left".this.pos, 1000."strongEaseOut");
};
var moveCommand;
moveBtn.onclick = function () {
var animate = new Animate(ball);
moveCommand = new MoveCommand(animate, pos.value);
moveCommand.execute();
};
Copy the code
Next, add the Undo button:
<div
id="ball"
style="position: absolute; background: #000; width: 50px; height: 50px"
></div>Enter the position of the ball after it moves:<input id="pos" />
<button id="moveBtn">Began to move</button>
<button id="cancelBtn">cancel</cancel> <! -- Add cancel button -->
Copy the code
Undo is typically implemented by adding a method called unexecude or undo to the command object, which performs the reverse of execute. Before executing the command. Execute method, record the current position of the ball. In the unexecude or undo operation, return the ball to the recorded position.
<script>
var ball = document.getElementById('ball');
var pos = document.getElementById('pos');
var moveBtn = document.getElementById('moveBtn');
var cancelBtn = document.getElementById('cancelBtn');
var MoveCommand = function (receiver, pos) {
this.receiver = receiver;
this.pos = pos;
this.oldPos = null;
};
MoveCommand.prototype.execute = function () {
this.receiver.start('left'.this.pos, 1000.'strongEaseOut');
this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName]; // Record the position of the ball before it starts moving
};
MoveCommand.prototype.undo = function () {
this.receiver.start('left'.this.oldPos, 1000.'strongEaseOut'); // Return to the position recorded before the ball moved
};
var moveCommand;
moveBtn.onclick = function () {
var animate = new Animate(ball);
moveCommand = new MoveCommand(animate, pos.value); moveCommand.execute();
};
cancelBtn.onclick = function () {
moveCommand.undo();// Undo the command
};
</script>
Copy the code
Undo is now easily implemented in command mode. If you did it with a normal method call, you might need to manually record the ball’s movement each time to get it back to its previous position. In command mode, the original position of the ball is stored as a property of the command object before the ball starts moving, so all you need to do is provide an undo method that tells the ball to move to the original position you just recorded.
Undo is a very useful feature in command mode, and it can also be used to implement the Ctrl+Z function of a text editor.
macros
Macro commands are a set of commands. You can execute a batch of commands at a time by executing macro commands. Next, step by step create a macro command.
Step1: Create various commands
var closeDoorCommand = {
execute: function () {
console.log('shut down'); }};var openPcCommand = {
execute: function () {
console.log('Turn on the computer'); }};var openQQCommand = {
execute: function () {
console.log('login QQ'); }};Copy the code
Step2: define MacroCommand
The macrocommand-add method adds subcommands to the macroCommand object. When the execute method of the macroCommand object is called, the set of subcommands is iterated over and their execute methods are executed in turn:
var MacroCommand = function () {
return {
commandsList: [].add: function (command) {
this.commandsList.push(command);
},
execute: function () {
for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); }}}};var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();
Copy the code
You can also add undo function to macro commands, similar to macrocommand-execute. When macrocommand-undo is called, all subcommand objects contained in the macroCommand must perform their undo operations in turn.
Intelligent commands and fool commands
The above closeDoorCommand does not contain any information about the receiver, and is itself responsible for executing the request, contrary to the previous command objects that contain a receiver.
Generally speaking, the command mode can save a receiver in the command objects to be responsible for real implementation of customer request, in this case the command object is “fool”, it is only responsible for the customer’s request to practitioners to perform, this model has the advantage of the initiator and request as much as possible to get a decoupling between the receiver.
However, we can also define some more “smart” command objects, “smart” command objects can directly implement the request, so that the recipient does not need to exist, such “smart” command objects are also called intelligent commands. Intelligent commands without receivers degenerate so much as policy patterns that they are indistinguishable from the structure of the code, only the difference in intent. The policy pattern points to a smaller problem domain, where the goal of all policy objects is always the same, they are just different means of achieving that goal, and their internal implementation is in terms of “algorithms.” While the intelligent command pattern points to a wider problem domain, the target of the command object is more divergent. Command mode can also complete undo, queuing and other functions.
One last word
If this article is helpful to you, or inspired, please help to like and follow it, your support is the biggest motivation for me to keep writing, thank you for your support.