Replace Conditional with Polymorphism

Written By Jesse Schutt
Posted on

This is a continuation of my previous article, in which I attempted to rework some of the examples into PHP from Martin Fowler's excellent book Refactoring. In this entry, we will replace a switch statement with polymorphism, using two approaches that have been successful for me in the past.

Replace Conditional with Polymorphism

If you've ever done any research into refactoring, or programming in general, you've most likely heard the term "polymorphism". When I first came across it, I have to admit, I was intimidated. Now that I've become more familiar with the concept, I can assure you, the word itself is more complicated than the underlying principle! 

Polymorphism, in this context, is loading and deferring functionality to the appropriate classes. There are a number of ways to determine which class to load, but I will demonstrate my methods of choice using the following snippet as a starting point.

<?php

namespace App\Services;

class Project
{

    private $type;
    private $baseDesignRate = 3;
    private $baseStrategyRate = 2;
    private $baseDevelopmentRate = 1;

    public function calculateRate()
    {
        switch ($this->type) {
            case 'design':
                if ($this->youHelp()) {
                    $rate = $this->getBaseDesignRate() * 5;
                } else {
                    $rate = $this->getBaseDesignRate();
                }
                break;

            case 'strategy':
                if ($this->youTrustUs()) {
                    $rate = $this->getBaseStrategyRate() * 0.5;
                } else {
                    $rate = $this->getBaseStrategyRate();
                }
                break;

            case 'development':
                if ($this->projectIsInLaravel()) {
                    $rate = $this->getBaseDevelopmentRate() * 0.8;
                } else {
                    $rate = $this->getBaseDevelopmentRate();
                }
                break;
        }

        return $rate;
    }

    // getters & setters omitted for brevity

This is a pretty typical approach to branching logic on an object, and you've likely gone this route in the past. Let's take a few minutes and see how we can apply polymorphism to replace the complex switch conditional and allow for flexible modifications in the future.

What is wrong with leaving the switch statement anyhow? 
In this case, every time a change is necessary on a Project Type we would need to modify a complex conditional. Polymorphism allows us to easily add or remove child classes without modifying the parent. #fistpump

Step 1 is to make sure the switch statement is in a method of its own. In our example, calculateRate() is only responsible for figuring out how much to charge for a project - so we are good there.

Step 2 is to move the method into an informatively named subclass, such as ProjectRateType. You will likely need to change some of the Project properties to protected now that we are extending the parent object.

<?php

namespace App\Services;

class ProjectRateType
{

    public function calculateRate(Project $project)
    {
        $rate = $project->getBaseRate();

        switch ($project->getType()) {
            case 'design':
                if ($project->youHelp()) {
                    $rate = $project->getBaseDesignRate() * 5;
                } else {
                    $rate = $project->getBaseDesignRate();
                }
                break;

            case 'strategy':
                if ($project->youTrustUs()) {
                    $rate = $project->getBaseStrategyRate() * 0.5;
                } else {
                    $rate = $project->getBaseStrategyRate();
                }
                break;

            case 'development':
                if ($project->projectIsInLaravel()) {
                    $rate = $project->getBaseDevelopmentRate() * 0.8;
                } else {
                    $rate = $project->getBaseDevelopmentRate();
                }
                break;
        }

        return $rate;
    }
}

Step 3 is to create a subclass for each leg of the conditional, overriding the parent calculateRate method. Take a look at the following:

class DesignRateType extends ProjectRateType
{

    public function calculateRate(Project $project)
    {
        if ($project->youHelp()) {
            $rate = $project->getBaseDesignRate() * 5;
        }  else {
            $rate = $project->getBaseDesignRate();
        }

        return $rate;
    }
}
class StrategyRateType extends ProjectRateType
{

    public function calculateRate(Project $project)
    {
        if ($project->youTrustUs()) {
            $rate = $project->getBaseStrategyRate() * 0.5;
        } else {
            $rate = $project->getBaseStrategyRate();
        }

        return $rate;
    }
}
class DevelopmentRateType extends ProjectRateType
{

    public function calculateRate(Project $project)
    {
        if ($project->projectIsInLaravel()) {
            $rate = $project->getBaseDevelopmentRate() * 0.8;
        } else {
            $rate = $project->getBaseDevelopmentRate();
        }

        return $rate;
    }
}

Step 4 is to turn ProjectRateType into either an interface or abstract class. calculateRate() will be deferred to the dynamically loaded child classes in step 5.

<?php

namespace App\Services;

abstract class ProjectRateType
{
    abstract public function calculateRate(Project $project);
}

Step 5

Now that we've created the individual classes for each of the conditional legs, we need to figure out how to load the correct class.

There are two methods I've come to use regularly in these situations. The first is a simple lookup array, and the second is a factory method.3 Let's start with the lookup array.

The Lookup Array Approach

Essentially, this tact requires some kind of direct mapping from the Project Type to the names of the subclasses. I typically push them into an array for referencing like this:

<?php

namespace App\Services;

class Project
{

    protected $type;
    protected $baseDesignRate = 3;
    protected $baseStrategyRate = 2;
    protected $baseDevelopmentRate = 1;

    public function calculateRate()
    {
        $lookupArray = [
            'design' => 'DesignRateType',
            'strategy' => 'StrategyRateType',
            'development' => 'DevelopmentRateType'
        ];

        if( ! array_key_exists($this->type, $lookupArray)) {
            throw new \RuntimeException('Incorrect project type');
        }

        $className = "App\\Services\\" . $lookupArray[$projectType];

        return ( new $className )->calculateRate($this);
    }

Then in your calling code you could do something like this:

$project = new Project;
$project->setType('development');
echo $project->calculateRate();

The Factory Approach

Admittedly, there is a fair bit of code that determines which class to load when using the lookup array approach. If you'd like to abstract that into another class, then the factory approach is your ticket.

A factory in PHP is responsible for creating objects. In our usage, we will pass a string (the project type) and the factory will build up and return the proper class.

Another upside to using the factory approach is that you can dynamically add classes without having to modify the mapping array utilized in the lookup array approach.

<?php

namespace App\Services;

class ProjectRateFactory {

    public function getProjectRateType($projectType)
    {
        $className = "App\\Services\\" . ucfirst($projectType) . "RateType";

        if( ! class_exists($className)) {
            throw new \RuntimeException('Incorrect project type');
        }
        
        return new $className;
    }
}

This factory assumes I've followed a convention of naming my child classes {projectType}RateType, so I simply build up a string to the child class, verify it exists, and return it. 

As a result, my calculateRate() method is reduced to this:

<?php

namespace App\Services;

class Project
{

    protected $type;
    protected $baseDesignRate = 3;
    protected $baseStrategyRate = 2;
    protected $baseDevelopmentRate = 1;

    private $simpleFactory;

    public function __construct(ProjectRateFactory $simpleFactory)
    {
        $this->simpleFactory = $simpleFactory;
    }

    public function calculateRate()
    {
        $projectRateType = $this->simpleFactory->getProjectRateType($this->type);

        return $projectRateType->calculateRate($this);
    }

And then we can acquire the project rate like this:

$project = new Project(new ProjectRateFactory());
$project->setType('development');
echo $project->calculateRate();

Conclusion

Without question, the hardest part for me to grasp in polymorphism is, surprisingly, not the concept of separating out logic into appropriate classes. For some reason that has always made sense. Conversely, it has been the mechanism for determining which class to load that has caused me grief! 

For now, I've landed on the two methods outlined above and have been pleased with the results.

I'm curious, what has been your approach? Ping me on twitter at @jesseschutt and let me know!

  1. Cover Image
  2. Quote background image
  3. The names of the approaches are purely subjective.