Chat
Ask me anything
Ithy Logo

Command Pattern in Java

A comprehensive guide to applying the Command design pattern with code examples

industrial control panel buttons

Highlights

  • Encapsulation: The Command pattern encapsulates a request as an object, decoupling sender and receiver.
  • Flexibility: It supports advanced features like undo/redo, queue management, and parameterization.
  • Modularity: The pattern enables clean organization by separating concrete commands from the invoker and receiver.

Introduction

The Command pattern is a behavioral design pattern that encapsulates a request as an object, thereby enabling parameterization of clients with different requests, as well as supporting operations like queuing, logging, and undo/redo functionalities. In Java, this design pattern helps decouple the object that issues a request (the invoker) from the object that knows how to execute it (the receiver). This decoupling leads to a flexible, modular, and maintainable codebase.

This guide will introduce the primary components of the Command pattern, illustrate its implementation with a simple example involving a light switch, and extend the discussion with a more advanced example to explain its practical applications.


Core Components of the Command Pattern

The Command pattern is comprised of five primary components:

Component Description
Command Interface Defines a standard method (typically called execute()) which all concrete commands must implement.
Concrete Command Implements the Command interface, binding a specific set of actions to a receiver.
Receiver The entity that actually performs the operation when the command’s execute() method is called.
Invoker The object that holds the command and triggers its execution, typically via a user action or event.
Client Creates the command object and binds it to the corresponding receiver.

Basic Example: Light Switch

Explanation

In our first example, we will simulate a simple light switch scenario where commands are utilized to turn a light on and off. By encapsulating these requests in command objects, our design is decoupled, allowing the invoker (in this case, a switch) not to directly reference the light's implementation. This design fosters flexibility, making it easier to modify behavior without altering the invoker.

The Command Interface

The Command interface defines a standard execute() method that all concrete commands must implement. This consistency enables the invoker to execute any command by simply calling the execute() method.


// Command Interface
public interface Command {
    void execute();
}
  

Receiver: Light

The receiver, represented by the Light class, contains the methods turnOn() and turnOff() which carry out the actual operations.


public class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }
    
    public void turnOff() {
        System.out.println("Light is OFF");
    }
}
  

Concrete Commands: LightOnCommand and LightOffCommand

The Concrete Command classes implement the Command interface, binding the receiver’s operations to their execute() method.


// Concrete Command for Turning the Light On
public class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.turnOn();
    }
}

// Concrete Command for Turning the Light Off
public class LightOffCommand implements Command {
    private Light light;
    
    public LightOffCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.turnOff();
    }
}
  

Invoker: The Switch

The invoker class, termed Switch in this example, contains a reference to the command. It invokes the command’s execute() method without needing any knowledge about the underlying operation.


public class Switch {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}
  

Client: Assembling the Components

The client is responsible for creating the receiver, concrete commands, and invoker. This is where the system is configured, and dependencies are managed.


public class CommandPatternExample {
    public static void main(String[] args) {
        // Creating the receiver
        Light livingRoomLight = new Light();
        
        // Creating concrete command objects
        Command turnOn = new LightOnCommand(livingRoomLight);
        Command turnOff = new LightOffCommand(livingRoomLight);
        
        // Setting up the invoker
        Switch lightSwitch = new Switch();
        
        // Executing commands through the invoker
        // Turning the light on
        lightSwitch.setCommand(turnOn);
        lightSwitch.pressButton();
        
        // Turning the light off
        lightSwitch.setCommand(turnOff);
        lightSwitch.pressButton();
    }
}
  

Advanced Example: Stock Trading System

Practical Application

For a more advanced example, we can consider a stock trading system that uses the Command pattern for executing and managing stock orders. Here, the system decouples the trading commands (buying and selling stocks) from the execution logic, allowing for features such as the logging of transactions, command queueing, and potential undo operations.

In this example:

  • An Order interface describes the standard operation for executing an order.
  • The Concrete Commands, such as BuyStock and SellStock, implement the execution of their specific stock operations.
  • A Stock class, functioning as the receiver, encapsulates the operations to buy or sell stocks.
  • A Broker, or invoker, maintains a list of orders and has the responsibility to execute them when required.

Step 1: Define the Order Interface


public interface Order {
    void execute();
}
  

Step 2: Create Concrete Commands


public class BuyStock implements Order {
    private Stock stock;
    
    public BuyStock(Stock stock) {
        this.stock = stock;
    }
    
    @Override
    public void execute() {
        stock.buy();
    }
}

public class SellStock implements Order {
    private Stock stock;
    
    public SellStock(Stock stock) {
        this.stock = stock;
    }
    
    @Override
    public void execute() {
        stock.sell();
    }
}
  

Step 3: Define the Receiver: Stock


public class Stock {
    private String name;
    private int quantity;
    
    public Stock(String name, int quantity) {
        this.name = name;
        this.quantity = quantity;
    }
    
    public void buy() {
        System.out.println("Stock [Name: " + name + ", Quantity: " + quantity + "] bought");
    }
    
    public void sell() {
        System.out.println("Stock [Name: " + name + ", Quantity: " + quantity + "] sold");
    }
}
  

Step 4: Create the Invoker: Broker


import java.util.ArrayList;
import java.util.List;

public class Broker {
    private List<Order> orderList = new ArrayList<>();
    
    public void takeOrder(Order order) {
        orderList.add(order);
    }
    
    public void placeOrders() {
        for (Order order : orderList) {
            order.execute();
        }
        orderList.clear();
    }
}
  

Step 5: Client Code


public class StockTradingDemo {
    public static void main(String[] args) {
        Stock abcStock = new Stock("ABC", 10);
        
        // Creating commands
        Order buyOrder = new BuyStock(abcStock);
        Order sellOrder = new SellStock(abcStock);
        
        // Creating the invoker
        Broker broker = new Broker();
        broker.takeOrder(buyOrder);
        broker.takeOrder(sellOrder);
        
        // Executing all orders
        broker.placeOrders();
    }
}
  

Benefits of Using the Command Pattern

The Command pattern provides several benefits that contribute to cleaner and more maintainable code architectures:

  • Decoupling of Sender and Receiver: The invoker does not need to understand how the action is handled, so changes in the receiver's functionality have minimal impact.
  • Support for Modular Extensions: Adding new commands for new functionalities is straightforward without modifying existing code or breaking the open/closed principle.
  • Enabling Undo/Redo Operations: Commands can be stored or reversed by keeping a history of operations, allowing for operations to be undone or redone seamlessly.
  • Enhanced Logging and Transaction Management: Since commands are treated as objects, it becomes easier to log actions or manage transactions, which is particularly useful in applications like stock trading.

Discussion of Key Concepts

Encapsulation and Independence

One of the hallmark advantages of the Command design pattern is its encapsulation of behavior into self-contained objects. This encapsulation allows developers to add, modify, or remove commands with minimal impact on other parts of the system. For example, in a graphical user interface, various user actions can be encapsulated as different command objects, ensuring that the user interface (invoker) remains decoupled from the business logic (receiver).

Enhanced Flexibility

By abstracting the request into an object, the Command pattern introduces a layer of indirection that promotes flexibility. This design enables developers to easily modify the request parameters, implement delayed command execution, or even maintain a history of executed commands for potential rollback operations. Moreover, this flexibility is a critical factor in designing systems that require dynamic behavior changes during runtime, such as complex event handling frameworks or multi-threaded task schedulers.

Real-World Applications

In the software industry, the Command pattern is commonly seen in applications such as:

  • GUI application controls: Menu items, buttons, and toolbar actions are frequently implemented using the Command pattern to separate the user interface logic from the underlying behavior.
  • Transactional systems: In financial applications, such as stock trading platforms, encapsulation of trade operations enables complex transactional control, ensuring that commands can be individually logged and potentially reversed.
  • Macro recording: In integrated development environments (IDEs) or text editors, sequences of commands can be recorded as macros that facilitate repetitive tasks.

Conclusion

The Command pattern is a powerful design tool that simplifies the way requests are encapsulated, ensuring that clients, invokers, and receivers remain decoupled from each other. By treating operations as objects, this pattern not only promotes clean, modular design but also provides a framework for extensibility and advanced features such as undo/redo functionalities, command logging, and transaction management. Through the examples provided – from a simple light switch system to a more complex stock trading mechanism – it is evident that the Command pattern can be effectively leveraged to build flexible and maintainable systems.

Implementing this pattern in Java requires a clear understanding of the roles played by the command interface, concrete commands, the receiver, the invoker, and the client. As demonstrated, these components work together cohesively to enable decoupled interactions and scalable enhancements. Overall, whether you are designing a small-scale application or architecting a large system, the Command pattern offers a robust solution to manage operations in a controlled and adaptable manner.


References


Recommended Queries


Last updated February 27, 2025
Ask Ithy AI
Download Article
Delete Article