Skip to content

Design Patterns: Command

Published: at 02:06 PM

Overview

The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging the requests. This pattern also supports undoable operations. The key components of the Command pattern are:

create
schedule
use
Client
Command
Invoker
Receiver

A command object contains all the necessary information to execute a request either immediately or at a later time.

Example

Consider the following scenario where we are creating a shopping website and we want to have a button that allows a user to add products to a basket. Using the command pattern we’d have the following structure:

create
schedule
use
Client

Button
Command

AddToBasketCommand
Invoker

CommandManager
Receiver

Basket

The AddToBasketCommand class encapsulates:

This command object holds all the pieces required to execute the command and verify its eligibility for execution. For example, if an item is out of stock, the command cannot be executed.

The command manager tracks all executed commands, enabling functionality such as undo and redo.

Implementing the Command Pattern

Let’s walk through implementing the Command pattern in a shopping basket application.

Initial Code

ShoppingBasket
| Program.cs
ShoppingBasket.Business
| Models
| Repositories

Here is the initial code:

// ShoppingBasket.Program

class Program
{
    static void Main(string[] args)
    {
        var shoppingBasketRepository = new ShoppingBasketRepository();
        var productsRepository = new ProductsRepository();

        var product = productsRepository.FindBy("bwb43hf");

        shoppingBasketRepository.Add(product);
        shoppingBasketRepository.IncreaseQuantity(product.ItemId);
        shoppingBasketRepository.IncreaseQuantity(product.ItemId);
        shoppingBasketRepository.IncreaseQuantity(product.ItemId);
        shoppingBasketRepository.IncreaseQuantity(product.ItemId);
        shoppingBasketRepository.IncreaseQuantity(product.ItemId);

        PrintBasket(shoppingBasketRepository);
    }

    static void PrintBasket(ShoppingBasketRepository shoppingBasketRepository)
    {
        // Code to print items in basket
    }
}

In the current implementation, the application directly interacts with the shopping basket and products using the Repository Pattern. It retrieves a product by its ID, increases the quantity of that product five times, and then prints the basket’s contents, which should reflect the product added five times.

While this approach works, it tightly couples the application logic with the repository. To improve flexibility and maintainability, we can apply the Command pattern. This will allow us to abstract the interaction with the repository into a separate layer, making the application code cleaner and more modular.

This code directly interacts with the repositories. To apply the Command pattern, we will introduce a layer that handles repository interactions, allowing the main application to simply invoke commands.

Creating the Command Interface

To begin applying the Command pattern we need to create a command interface. This interface serves as a contract, defining how the command manager will interact with various commands.

First, create a Commands folder:

ShoppingBasket
| Program.cs
ShoppingBasket.Business
| Commands
| Models
| Repositories

Next, define the ICommand interface:

// ShoppingBasket.Business.Commands.ICommand

interface ICommand
{
    void Execute();
    bool CanExecute();
    void Undo();
}

Command Manager

Now, let’s introduce the CommandManager, which is generic enough to handle various types of commands.

// ShoppingBasket.Business.Commands.CommandManager

public class CommandManager
{
    private Stack<ICommand> _commands = new Stack<ICommand>();

    public void Invoke(ICommand command)
    {
        if (command.CanExecute())
        {
            _commands.Push(command);
            command.Execute();
        }
    }

    public void Undo()
    {
        while (_commands.Count > 0)
        {
            var command = _commands.Pop();
            command.Undo();
        }
    }
}

The CommandManager uses a stack to keep track of executed commands, following a Last-In-First-Out (LIFO) order, which is ideal for undo operations.

Implementing Commands

Now, let’s create the AddToBasketCommand class:

// ShoppingBasket.Business.Commands.AddToBasketCommand

public class AddToBasketCommand : ICommand
{
    private readonly IShoppingBasketRepository _shoppingBasketRepository;
    private readonly IProductRepository _productRepository;
    private readonly Product _product;

    public AddToBasketCommand(
        IShoppingBasketRepository shoppingBasketRepository,
        IProductRepository productRepository,
        Product product)
    {
        _shoppingBasketRepository = shoppingBasketRepository;
        _productRepository = productRepository;
        _product = product;
    }

    public bool CanExecute()
    {
        return _product != null && _productRepository.GetStockFor(_product.ItemId) > 0;
    }

    public void Execute()
    {
        if (_product == null) return;

        _productRepository.DecreaseStockBy(_product.ItemId, 1);
        _shoppingBasketRepository.Add(_product);
    }

    public void Undo()
    {
        if (_product == null) return;

        var lineItem = _shoppingBasketRepository.Get(_product.ItemId);
        _productRepository.IncreaseStockBy(_product.ItemId, lineItem.Quantity);
        _shoppingBasketRepository.RemoveAll(_product.ItemId);
    }
}

This class follows the ICommand interface, ensuring it can execute, check execution eligibility, and undo its action.

Additional Commands

We’ll also need a command for modifying the item quantity:

// ShoppingBasket.Business.Commands.ChangeQuantityCommand

public class ChangeQuantityCommand : ICommand
{
    public enum Operation
    {
        Increase,
        Decrease
    }

    private readonly Operation _operation;
    private readonly IShoppingBasketRepository _shoppingBasketRepository;
    private readonly IProductRepository _productRepository;
    private readonly Product _product;

    public ChangeQuantityCommand(
        Operation operation,
        IShoppingBasketRepository shoppingBasketRepository,
        IProductRepository productRepository,
        Product product)
    {
        _operation = operation;
        _shoppingBasketRepository = shoppingBasketRepository;
        _productRepository = productRepository;
        _product = product;
    }

    public bool CanExecute()
    {
        switch (_operation)
        {
            case Operation.Decrease:
                return _shoppingBasketRepository.Get(_product.ItemId).Quantity != 0;
            case Operation.Increase:
                return _productRepository.GetStockFor(_product.ItemId) > 0;
            default:
                return false;
        }
    }

    public void Execute()
    {
        switch (_operation)
        {
            case Operation.Decrease:
                _productRepository.IncreaseStockBy(_product.ItemId, 1);
                _shoppingBasketRepository.DecreaseQuantity(_product.ItemId);
                break;
            case Operation.Increase:
                _productRepository.DecreaseStockBy(_product.ItemId, 1);
                _shoppingBasketRepository.IncreaseQuantity(_product.ItemId);
                break;
        }
    }

    public void Undo()
    {
        switch (_operation)
        {
            case Operation.Decrease:
                _productRepository.DecreaseStockBy(_product.ItemId, 1);
                _shoppingBasketRepository.IncreaseQuantity(_product.ItemId);
                break;
            case Operation.Increase:
                _productRepository.IncreaseStockBy(_product.ItemId, 1);
                _shoppingBasketRepository.DecreaseQuantity(_product.ItemId);
                break;
        }
    }
}

This command handles increasing or decreasing the quantity of an item in the basket.

Refactoring the Application

With these commands in place, we can refactor the main application to use the Command pattern:

// ShoppingBasket.Program

class Program
{
    static void Main(string[] args)
    {
        var shoppingBasketRepository = new ShoppingBasketRepository();
        var productsRepository = new ProductsRepository();

        var product = productsRepository.FindBy("bwb43hf");

        var addToBasketCommand = new AddToBasketCommand(
            shoppingBasketRepository,
            productsRepository,
            product
        );

        var increaseQuantityCommand = new ChangeQuantityCommand(
            ChangeQuantityCommand.Operation.Increase,
            shoppingBasketRepository,
            productsRepository,
            product
        );

        var manager = new CommandManager();

        manager.Invoke(addToBasketCommand);
        manager.Invoke(increaseQuantityCommand);
        manager.Invoke(increaseQuantityCommand);
        manager.Invoke(increaseQuantityCommand);
        manager.Invoke(increaseQuantityCommand);
        manager.Invoke(increaseQuantityCommand);

        PrintBasket(shoppingBasketRepository);
    }

    static void PrintBasket(ShoppingBasketRepository shoppingBasketRepository)
    {
        // Code to print items in basket
    }
}

This refactoring eliminates direct interaction with repositories, instead leveraging the Command pattern to handle these interactions.

Summary

The Command pattern is composed of the following elements:

The Command pattern introduces a flexible layer that separates command execution from the application logic, enhancing the system’s testability and robustness. It also facilitates features like undo/redo. However, it can add complexity, which may not always be necessary depending on the application’s needs.