The Command Pattern is a powerful Behavioral Design Pattern that’s incredibly useful when you want to encapsulate actions (or requests) as objects. It shines in scenarios like Undo/Redo, macro commands, queueing, or GUI button click handling.
Here’s your complete blog article on the Command Pattern in Dart, fully structured and explained with runnable code — perfect for your design pattern blog series.
What is the Command Pattern?
The Command Pattern is a behavioral design pattern in which an object encapsulates a request as a command, allowing you to:
- Parameterize clients with different requests,
- Queue or log commands,
- Support undoable operations.
In simple terms:
“Turn a request (e.g., save, copy, print) into an object.”
When to Use the Command Pattern?
- To encapsulate an operation as a reusable object.
- To support undo/redo functionality.
- To log or queue actions for later execution.
- To decouple the object that issues a request from the one that performs it.
Example in Dart: Remote Control Command System
Scenario:
You’re building a smart remote control that can perform actions like:
- Turning a light on/off
- Turning a fan on/off
We’ll encapsulate these actions using the Command Pattern.
Step 1: Define the Command Interface
abstract class Command {
void execute();
void undo();
}
Step 2: Create Receiver Classes (Actual devices)
class Light {
void turnOn() => print("Light is ON");
void turnOff() => print("Light is OFF");
}
class Fan {
void start() => print("Fan is ON");
void stop() => print("Fan is OFF");
}
Step 3: Create Concrete Command Classes
class LightOnCommand implements Command {
final Light light;
LightOnCommand(this.light);
@override
void execute() => light.turnOn();
@override
void undo() => light.turnOff();
}
class FanOnCommand implements Command {
final Fan fan;
FanOnCommand(this.fan);
@override
void execute() => fan.start();
@override
void undo() => fan.stop();
}
Step 4: Invoker (Remote Control)
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
void pressUndo() {
_command?.undo();
}
}
Step 5: Client Code
void main() {
var remote = RemoteControl();
var light = Light();
var fan = Fan();
var lightCommand = LightOnCommand(light);
var fanCommand = FanOnCommand(fan);
remote.setCommand(lightCommand);
remote.pressButton(); // Light is ON
remote.pressUndo(); // Light is OFF
remote.setCommand(fanCommand);
remote.pressButton(); // Fan is ON
remote.pressUndo(); // Fan is OFF
}
🧪 Output:
Light is ON
Light is OFF
Fan is ON
Fan is OFF
Explanation
- The Command interface defines
execute()andundo()methods. - Concrete commands encapsulate actions and their receivers.
- The Invoker (RemoteControl) is decoupled from the actual logic.
- The Receiver (Light/Fan) performs the real work.
- The Client wires everything together and controls execution.
Benefits of the Command Pattern
- Decouples sender and receiver of actions.
- Enables easy support for undo/redo.
- Can queue or delay operations.
- Easy to implement macro commands (i.e., composite commands).
Drawbacks
- Requires more classes than simpler solutions.
- Slightly overkill for small actions if undo/history isn’t needed.
- Harder to trace direct call chains.
Flutter Use Cases
- Command-style architecture for state actions (similar to Redux).
- GUI component event handlers (e.g., button press = command).
- Task queues, job schedulers, or delayed operations.
- Undo/Redo in text editors, drawing apps, etc.
Summary Table
| Attribute | Command Pattern |
|---|---|
| Purpose | Encapsulate requests as objects |
| Common Use Cases | GUI actions, undo/redo, queues |
| Easy in Dart? | ✅ Yes, using classes & interfaces |
| Useful in Flutter? | ✅ Especially for undo/history or Redux-like setups |


