The Command design pattern is a behavioral design pattern that encapsulates a
request as an object, thereby allowing for parameterization of clients with
different requests, queuing of requests, and logging of requests, among other
things. This pattern decouples the sender and receiver of a command by introducing
an intermediate object (command object) that encapsulates all the information
necessary for executing a particular action or request. This promotes loose
coupling and can enhance flexibility and extensibility in a software system.
Let's break down the Command design pattern in C++:
Participants in the Command Pattern:
Command: This is an interface or an abstract class that declares an execute()
method. Concrete command classes implement this interface, encapsulating a specific
action or request.
ConcreteCommand: These are concrete implementations of the Command interface. They
define the binding between a specific action and the receiver object. These objects
store the receiver object and the parameters necessary for executing the command.
Invoker: This is responsible for invoking commands. It does not know how to perform
the command but delegates this responsibility to the command objects.
Receiver: This is the object that knows how to perform the operations associated
with carrying out a request. It is the target of the command and is decoupled from
the invoker and the command objects.
Client: Creates ConcreteCommand objects and sets their receivers.
Example Implementation:
Let's consider a simple example of a command pattern where we have a light and we
want to control it using commands like TurnOn and TurnOff.
cpp
Copy code
#include <iostream>
#include <memory>
// Command Interface
class Command {
public:
virtual void execute() = 0;
};
// Receiver
class Light {
public:
void turnOn() {
std::cout << "Light is ON\n";
}
void turnOff() {
std::cout << "Light is OFF\n";
}
};
// Concrete Command classes
class TurnOnCommand : public Command {
private:
std::shared_ptr<Light> light;
public:
TurnOnCommand(std::shared_ptr<Light> l) : light(l) {}
void execute() override {
light->turnOn();
}
};
class TurnOffCommand : public Command {
private:
std::shared_ptr<Light> light;
public:
TurnOffCommand(std::shared_ptr<Light> l) : light(l) {}
void execute() override {
light->turnOff();
}
};
// Invoker
class RemoteControl {
private:
std::shared_ptr<Command> command;
public:
void setCommand(std::shared_ptr<Command> cmd) {
command = cmd;
}
void pressButton() {
if (command) {
command->execute();
} else {
std::cout << "No command set\n";
}
}
};
int main() {
// Create Receiver
std::shared_ptr<Light> light = std::make_shared<Light>();
// Create Concrete Commands
std::shared_ptr<Command> turnOn = std::make_shared<TurnOnCommand>(light);
std::shared_ptr<Command> turnOff = std::make_shared<TurnOffCommand>(light);
// Create Invoker
RemoteControl remote;
// Execute commands using invoker
remote.setCommand(turnOn);
remote.pressButton(); // Output: Light is ON
remote.setCommand(turnOff);
remote.pressButton(); // Output: Light is OFF
return 0;
}
In this example:
Command is the abstract class with the execute() method.
Light is the receiver class that performs the actual actions (turnOn() and
turnOff()).
TurnOnCommand and TurnOffCommand are concrete command classes that encapsulate the
actions and the receiver.
RemoteControl is the invoker class that can be set with different commands and can
execute them.
By using the Command pattern, the client code (main() function in this case) does
not directly call the methods on the receiver (Light), instead, it operates through
the invoker (RemoteControl) and the command objects (TurnOnCommand and
TurnOffCommand). This separation allows for greater flexibility and extensibility
in the system, making it easier to add new commands or modify existing ones without
affecting other parts of the code.