June 17, 2026 in patterns5 minutes
A intermediate-level guide to Command: before-and-after java code and diagrams for a CS student.
Imagine you are building a text editor. You have a ToolbarButton that needs to respond when a user clicks it. At first, it only needs to trigger one action: saving the document. But soon, the requirements grow. Now the button must also handle printing and deleting.
In a naive implementation, the button becomes responsible for knowing exactly what every other part of the system does. It has to check which type of action was requested and then call the specific method on the Document object. This creates a “God Object” smell where the button is doing too much work—it is no longer just a UI trigger; it is now an orchestrator of business logic.
class Document {
void save() { System.out.println("Saving document..."); }
void print() { System.out.println("Printing document..."); }
void delete() { System.out.println("Deleting document..."); }
}
class ToolbarButton {
private final Document doc;
public ToolbarButton(Document doc) { this.doc = doc; }
// The button must know about every specific operation of the Document.
// Adding a new action requires modifying this class (violates Open/Closed).
public void handleClick(String actionType) {
if ("save".equals(actionType)) {
doc.save();
} else if ("print".equals(actionType)) {
doc.print();
} else if ("delete".equals(actionType)) {
doc.delete();
}
}
}As seen above, the ToolbarButton is tightly coupled to the Document. It must know that save(), print(), and delete() exist. If you add a “Format” feature, you have to go back into the button class and add another else if block. This violates the Open/Closed Principle, which states that classes should be open for extension but closed for modification.
classDiagram
direction BT
class ToolbarButton {
+handleClick(String actionType)
}
class Document {
+save()
+print()
+delete()
}
ToolbarButton ..> Document : calls methods via string check
To fix this, we use the Command pattern. Instead of passing a “request type” (like a String) to the button, we pass an object that represents the request itself.
Think of a restaurant: A customer doesn’t walk into a kitchen and start cooking; they hand a written order slip to a waiter. The waiter (the Invoker) doesn’t need to know how to cook a steak; they only need to deliver that slip to the chef (the Receiver). Once the chef reads the slip, they perform the actual work.
In this pattern:
execute() method).ToolbarButton), which triggers the command.Document), who knows how to perform the actual work.By treating the request as a standalone object, we achieve decoupling. The button no longer cares what happens when it is clicked; it only knows that it has an object with an execute() method.
interface Command {
void execute();
}
class Document {
void save() { System.out.println("Saving document..."); }
void print() { System.out.println("Printing document..."); }
void delete() { System.out.println("Deleting document..."); }
}
// Concrete Command for saving
class SaveCommand implements Command {
private final Document doc;
public SaveCommand(Document doc) { this.doc = doc; }
@Override public void execute() { doc.save(); }
}
// Concrete Command for printing
class PrintCommand implements Command {
private final Document doc;
public PrintCommand(Document doc) { this.doc = doc; }
@Override public void execute() { doc.print(); }
}
// The ToolbarButton (Invoker) is now decoupled from the Document (Receiver).
// It only knows how to trigger a Command.
class ToolbarButton {
private final Command command;
public ToolbarButton(Command command) { this.command = command; }
public void handleClick() {
command.execute();
}
}Now, the relationship is transformed. The ToolbarButton interacts with an abstraction (Command) rather than a concrete implementation (Document). To add a new feature like “Format,” you simply create a new class that implements the Command interface. You never have to touch the ToolbarButton code again.
classDiagram
class Command {
<<interface>>
+execute()
}
class SaveCommand {
-Document doc
+execute()
}
class PrintCommand {
-Document doc
+execute()
}
class ToolbarButton {
-Command command
+handleClick()
}
class Document {
+save()
+print()
}
Command <|.. SaveCommand : implements
Command <|.. PrintCommand : implements
ToolbarButton o-- Command : holds reference to
SaveCommand --> Document : calls
PrintCommand --> Document : calls
When a user clicks the button, a specific sequence of events occurs. The button tells the command to execute, and that command then directs the receiver to perform its task:
sequenceDiagram
participant B as ToolbarButton
participant C as SaveCommand
participant D as Document
B ->> C: handleClick()
C ->> C: execute()
C ->> D: save()
D -->> C:
C -->> B:
The Command pattern is powerful because it turns a method call into a first-class citizen. While this example shows basic execution, treating actions as objects allows you to do more advanced things, such as storing commands in a list to implement “Undo/Redo” functionality or placing them in a queue to be processed later.
However, there is a cost: complexity. Every new action now requires a new class. If your application only has two buttons that will never change, creating an interface and multiple concrete classes is likely overkill and introduces unnecessary boilerplate. Use Command when you need to decouple the trigger from the logic or when you need to manage the lifecycle of an action (like queuing or undoing it).
Using the decoupled commands
Document myDoc = new Document();
ToolbarButton saveBtn = new ToolbarButton(new SaveCommand(myDoc));
ToolbarButton printBtn = new ToolbarButton(new PrintCommand(myDoc));
saveBtn.handleClick(); // Output: Saving document...
printBtn.handleClick(); // Output: Printing document...