Design Patterns in Dart: Command Pattern

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() and undo() 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

AttributeCommand Pattern
PurposeEncapsulate requests as objects
Common Use CasesGUI actions, undo/redo, queues
Easy in Dart?✅ Yes, using classes & interfaces
Useful in Flutter?✅ Especially for undo/history or Redux-like setups

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Lên đầu trang