In a recent practice of code exercises from CodeSignal, I came upon an interesting problem of designing up a fictious cloud storage service which is able to accept a list of commands and its parameters to update the storage service.
The commands are provided in a list for example:
The commands are to be parsed sequentially. Each command either updates the storage state or returns an error message of why its not successful. The returned responses are in the form of boolean strings i.e. ‘true’ or ‘false’.
Given the above sequence, a file of file size ‘10’ is to be added to the storage. Next, a copy of it is to be made and stored into storage. The final storage state and reults become:
My initial attempt was to use a single function with an if-else loop. It iterates over the command lists and parses each command into its name and parameters, which are delegated to additional functions to perform the operation with its input parameters. While it works in the first stages, the code became unwiedly by the second stage as more actions are added to the command list.
I started to research into using design patterns to refactor the code and came across the Strategy Pattern. The strategy pattern allows you to define a set of algorithms in its own bespoke classes and allow them to be interchageable at runtime. This is made possible through the use of a context object which defines the algorithm or strategy to delegate to at runtime.
We define a base class of Strategy which the algorithms can inherit from. There is only a single function of do_work which accepts the input parameters:
Next, we define the individual algorithms or strategies:
We define two subclass of AddFile and CopyFile as per the example inputs. Each class overrides the do_work abstract function to define its own functionality. For AddFile, we check if the file exists in storage. If so, we return ‘false’ with an error message. For CopyFile, we check that the source and destination filenames are not the same and that the source file exists beforehand.
Next, we define the context class which has a reference to the strategy object to use at runtime:
The Context class takes in an initial strategy object or None. We define the same do_work function which delegates the actual work to the underlying strategy object reference and returns its results.
The complete refactored code is as follows:
The following link provides a more detailed explanation of the Strategy pattern.