How I used the strategy pattern to tidy up some legacy code

John Richardson

A pragmatic software developer with ~ 10 years experience. PHP/Vue JS advocate.

@JRdevelop January 23, 2015

I was recently hired to add some new functionality to an existing PHP application, built using ZF1. The application was developed quite some time ago, and parts of the code-base are slightly dated. After discussion with the client, it was decided early on that as well as adding the new functionality, I would make an effort to improve the existing code. Read on for an overview of how I achieved this.

A quick overview of the job.

Just so you have some context, the client had a ‘sync’ service which ran once a week. It was used to add new data from their CMS to their API, and due to the amount of data involved, it had become unwieldy – taking around 5-6 hours to complete. My job was to enhance the process, making it possible to run a partial sync. As an example, it was a requirement that it should be possible to run a partial sync by, for example, a specific category, or within a specific date range.

When the code was written originally, it had been assumed that there would only be 1 type of sync. Because of this, it had not been coded in an extensible way, and in fact, a lot of the logic was simply dumped in the controller. My options were either, continue with this approach, dumping a bunch of conditional statements into the controller, or come up with an alternative solution. I decided on the latter.

How I addressed the problem.

First things first, I had to get my head around the problem, and the original code. After spending some time reviewing everything, and getting comfortable with the code, I came up with the following plan.

I knew I was going to need a way to run multiple algorithms/chunks of code, I knew there was a possibility of further sync types being added in future and I knew that I wanted to avoid the current mess of having a bunch of logic in the controller. With all this in mind, I decided the strategy pattern would be a good solution. I’ll talk about the strategy pattern later on, so if you’re unfamiliar with it, just bear with me.

Anyway, once this decision had been made, I came up with the following high level plan.

  • Create the new interface – I like to start of with the interface since it solidifies the requirements in my mind and also acts as a talking point for myself and the client. Had I started off with the back-end logic, there’s more chance I’d have forgotten something. I’m speaking from experience on this!
  • Talk over the interface & requirements with the client – moving on after having it signed off.
  • Replace the existing code in the controller with something more extensible(using the strategy pattern).
  • Once the above groundwork was in place, code the strategy class for each individual sync – one class per sync type.
  • Test – unit testing first, browser testing second.

The interface

Time for some visuals!

As discussed previously, a number of different sync options were required. I decided to display these options in a tab based interface, as seen below

 The category tab:

It was a requirement that multiple categories could be selected. This was achieved via use of a select input, enhanced using the ‘Select2’ library

The date range tab: 

The aim with the date range tab, was to make it as user friendly as possible. This was achieved through use of a pop-up modal window, made visible when clicking on the input field. The modal offers two calendars, making it possible to select a start and end date. At no point does the user need to worry about the format of the date

So, that’s the new interface in a nutshell. As you can see there are 4 different sync types available, each having it’s own set of options. These options of course needed to be handled by the back-end, and if I had stuck with the current solution I would have ended up with something similar to the code seen below.

public function runSyncAction() {
    
    if($syncType = 'full') {
        
    }
    else if($syncType = 'category') {
        
    }
    else if($syncType = 'datastore') {

    }
    else if($syncType = 'date-range') {

    }
}

At first glance, this may not look terrible. But as you can imagine, each of these conditional statements would have quite a bit of code nested within them, making it hard to follow (and harder still to test). On top of that, we end up with a bunch of logic inside the controller that simply does not belong there. And finally, when the time comes to add a new sync type, I would need to make changes to the controller itself, meaning I’d need to re-test it. Enter the strategy pattern.

The strategy pattern

In a nutshell, the strategy pattern is used to make a number of algorithms interchangeable. This is achieved by encapsulating each individual algorithm in it’s own class, and ensuring each of the classes shares a common interface.

As stated by, Wikipedia:

The strategy pattern

  • defines a family of algorithms,
  • encapsulates each algorithm, and
  • makes the algorithms interchangeable within that family.

Having used the pattern previously, I knew it would be a good match for the problem at hand and went with it. I can’t show the exact code, since I was working on a proprietary system, but the snippets below give a good indication of how the code looks.

First of all is the base class:

abstract class AbstractSyncStrategy{

    /**
     * @var String
     */
    protected $sharedProperty1;

    /**
     * @param $request
     * @return bool
     */
    public function ParseOptions($request) {

    }

}

Having thought about the various sync types, I was aware that some code duplication was likely. To avoid this, I decided to go with an abstract base class which all strategies inherit from.

Enforcing the public interface:

abstract class AbstractSyncStrategy implements SyncStrategy {

    /**
     * @var String
     */
    protected $sharedProperty1;

    /**
     * @param $request
     * @return bool
     */
    public function ParseOptions($request) {

    }

}

interface SyncStrategy {
    public function ParseOptions($request);
    public function Run();
}

Whilst an interface is not strictly needed here (since I’m using a base class and could just implement the methods within the base class), I decided an interface to be preferable as it clearly enforces and documents the public interface.

The individual strategies:

class CategorySyncStrategy extends AbstractSyncStrategy {

    /**
     * @param $request
     * @return bool
     */
    public function ParseOptions($request) {

    }

    /**
     * Runs the sync
     *
     * @return \API\Entity\Sync
     */
    public function Run() {

    }
}

As you can see the class has been gutted, but you can still get a feel for how it might work.

The class inherits from the base class & implements the Run/ParseOptions methods. The ‘run’ method is where the magic happens, containing the code that will execute, in this example, the category sync. Just to clarify, each sync type has its own class just like this.

The context class:

class SyncContext {
    /**
     * @var SyncStrategy
     */
    private $strategy;

    public function __construct(SyncStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function runStrategy() {
        $this->strategy->Run();
    }
}

The context class consumes a strategy class, and is responsible for executing the algorithm. It is used to decouple the algorithms from the client, which in this example would be the controller.

A comparison between the old methodology, and the new.

Had I stuck with the original methodology, I would have been left with a bloated controller. A controller handling much more than it should. I gave a brief example earlier, which is included again below for clarity.

public function runSyncAction() {
    
    if($syncType = 'full') {
        
    }
    else if($syncType = 'category') {
        
    }
    else if($syncType = 'datastore') {

    }
    else if($syncType = 'date-range') {

    }
}

Instead of the above, after using the strategy pattern, the controller now like this:

public function runSyncAction() {
    $strategy = new SyncFactory($_POST['strategy']);
    $context = new SyncContext($strategy);
    $context->run();
}

Again, this is pseudo code, rather than the code I actually wrote, but it works in much the same way. A strategy is instantiated based on the sync type selected, it is passed into the context and then the context executes it.

As you can see, the code that runs the sync has been abstracted away. It is encapsulated within it’s own class and is isolated from the rest of the code base. This makes it easier to debug & much easier to test. Additionally, imagine you wanted to add a new sync type. All you would need to do is update the interface, and then add a new strategy. You don’t have to touch the client (controller in this case) at any point. And rightly so!

Thanks for reading. I’ll be posting more content similar to this as I take on jobs so stay tuned.