Design Pattern – Command

The command pattern is useful to handle direction from args in command line app. 

For example, there was an ugly code as below, which I wrote a while ago. As I simplified the code to focus on if-else statement, it may look not that bad.  But, I was struggling with managing the mapping each method with the command from argument especially when adding a new one or modifying the existing one. 

Many said there is code smell when you see a long if-else or case statement. I didn’t know what was the problem until I fixed it. 

if (runOption == "all")
                {
                    await _stockPriceManager.ReadStockPriceFromWeb(stocksToRead, _appSettings.DaysToReadFromWeb);

                    // Iterate simulation
                    var averageAnnualReturn = await IterateSimulationAsync(investDay, stocksToAnalyze, _tradeOption);
                    
                }
                else if (runOption == "recalculate")
                {
                    await _stockPriceManager.ReCalculateCoefficient(stocksToAnalyze);
                }
                else if (runOption == "simulate")
                {
                    var averageAnnualReturn = await IterateSimulationAsync(investDay, stocksToAnalyze, _tradeOption);
                }
                else if (runOption.ToLower() == "readprice")
                {
                    await _stockPriceManager.ReadStockPriceFromWeb(stocksToRead, _appSettings.DaysToReadFromWeb);
                }

 

This code refactored as below using the command pattern. After refactoring, the selection condition was deligated to the concrete command object. To add a new action, it’s possible just to add new command object to the availablecommands array and no need to modify the code below that. Another and actually more important benefit that I didn’t show from this simple example code is that I could separate responsibility of each command under its own class. As you may imagine, most of those were inside the same class where if-else statement was. Implementing the command pattern naturally led me to implement the single responsibility principle. 

The downside of this approach is that you need to create a new 3 command class and one ICommand interface. When it’s a simple program like a prototype, we might want to just use a simple if-else style. But, if there is any chance the code become a long-running one, which implies that continuous add/modification will be followed by, I would use command pattern. 

                var availableCommands = new ICommand[]
                {
                    new UpdatePrice(_stockPriceManager, stocksToRead, _appSettings.DaysToReadFromWeb),
                    new UpdateStats(_stockPriceManager, stocksToAnalyze),
                    new SimulateCommand(_stockPriceManager, stocksToAnalyze, _marketWatchRepository, investDay,
                        _tradeOption, _logger, _appSettings)
                };

                if (runOption == "all")
                {
                    availableCommands.Select(async c => await c.Execute());
                }
                else
                {
                    var command = availableCommands.Single(c => c.Name == runOption);
                    await command.Execute();
                }

 

This was possible because each command shares the same interface, ICommand. That looks like below. 

    public interface ICommand
    {
        string Name { get; }

        Task Execute();
    }

 

One of the implementations of ICommand will look like below. 

    public class UpdateStats : ICommand
    {
        public string Name => "UpdateStats";

        private readonly IStockPriceManager _stockPriceManager;
        private readonly List<string> _stocksToAnalyze;

        public UpdateStats(IStockPriceManager stockPriceManager, List<string> stocksToAnalyze)
        {
            _stockPriceManager = stockPriceManager;
            _stocksToAnalyze = stocksToAnalyze;
        }

        public async Task Execute()
        {
            await _stockPriceManager.ReCalculateCoefficient(_stocksToAnalyze);
        }
    }

The key here is that all command object share the member, Name, and method, Execute() so that these commands can be used in a standardized manner. 

To see more about the command pattern, go to https://www.dofactory.com/net/command-design-pattern

Leave a comment

Your email address will not be published.