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:
- Command: Encapsulates the action and its parameters.
- Receiver: The object that knows how to perform the actual action.
- Invoker: Executes the command and keeps track of executed commands, enabling undo functionality.
- Client: Creates the command and assigns it to the invoker.
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:
The AddToBasketCommand
class encapsulates:
- A reference to the product(s) to be added.
- A reference to the shopping basket.
- Logic to check stock availability.
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:
- Command: Contains the instructions for an action.
- Receiver: The target object of the command.
- Invoker: Executes the command.
- Client: Determines which command to execute.
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.