DEV Community

loading...

🛠Refactoring: Replacing Conditional with Polymorphism

Lucas Fonseca Mundim
Software Engineer, geek, gamer and tech enthusiast in general
・3 min read

Switch statements are often considered code smells and should be avoided


The Problem

image
Suppose you have a class with various subtypes defined by a property such as Bird:

public class Bird
{
    // whatever properties do birds have
    public BirdType Type { get; set; }
}

public enum BirdType
{
    African,
    American,
    European
}
Enter fullscreen mode Exit fullscreen mode

And then you need to retrieve, lets say, the speed of a given bird. Thing is: the speed of the bird depends of it's type. Some logic is required, but it sounds easy enough right? You create a class method:

public class Bird
{
    // whatever properties do birds have
    public BirdType Type { get; set; }

    public double GetSpeed()
    {
        switch(Type)
        {
            case African:
                // logic for African birds
            case American:
                // logic for American birds
            case European:
                // logic for European birds
            default:
                // default logic
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A little bit ugly, but it gets the job done right? But here's the catch: after some time in production, you need to add a new type of bird, lets say Asian

public enum BirdType
{
    African,
    American,
    European,
    Asian // new type
}
Enter fullscreen mode Exit fullscreen mode

And now you have to calculate it's speed as well. "Just add another case to the switch statement" you might say. You could do that, yes, but bear in mind that this will violate SOLID's Open-Closed Principle! And we don't want that, do we?


The Solution: Polymorphism

With Object-Oriented Programming we have the concept of inheritance and, with that, we can implement polymorphism.

To the uninitiated, polymorphism is the ability to process objects differently depending on their class. Sounds familiar?

How do we approach the above problem with polymorphism in mind? We start very much the same: defining the Bird class, but with one change:

public abstract class Bird
{
    public abstract double GetSpeed();
}
Enter fullscreen mode Exit fullscreen mode

Now we have added the abstract modifier to the GetSpeed method and the Bird class itself. What that means is that the Bird class itself has no implementation for that method, nor can it be directly instantiated, and any subclasses might implement it themselves. Now all we gotta do is implement the subclasses!

public class AfricanBird : Bird
{
    public override double GetSpeed()
    {
        // logic for African birds
    }
}

public class AmericanBird : Bird
{
    public override double GetSpeed()
    {
        // logic for American birds
    }
}

public class EuropeanBird : Bird
{
    public override double GetSpeed()
    {
        // logic for European birds
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's see how that plays on a running code:

// Ran on C# Interactive via VisualStudio
> public abstract class Bird
. {
.     public abstract double GetSpeed();
. }
> public class AfricanBird : Bird
. {
.     public override double GetSpeed()
.     {
.         return 1;
.     }
. }
> public class AmericanBird : Bird
. {
.     public override double GetSpeed()
.     {
.         return 0.7;
.     }
. }
> Bird africanBird = new AfricanBird();
> Bird americanBird = new AmericanBird();
> africanBird.GetSpeed()
1
> americanBird.GetSpeed()
0.7
Enter fullscreen mode Exit fullscreen mode

Note that as the method we used is declared by Bird, we could define the variables as the base class and still use it, and we got our desired result 😉

And what if we need to add a new type of bird? We just add a new subclass! No need to modify any existing class! We have respected the Open-Closed Principle!

  • Note: This refactor that was done is called Replace conditional with Polymorphism (pretty easy to remember the name huh?)

Benefits

  • The forementioned compliance with the Open-Closed Principle
  • Gets rid of duplicate code, as you can get rid of many conditionals and/or switch statements
  • Adheres to the Tell, Don't Ask principle: the object itself is telling you what you want to know, instead of you having to "ask" (via conditionals) for the information

Reference

Discussion (1)

Collapse
jwhenry3 profile image
Justin Henry

I love converting conditionals to polymorphism, especially in reduced contexts:

const speeds = {
  african: 10,
  american: 7,
  european: 8
}
const bird = 'african'
const speed = speeds[bird]
Enter fullscreen mode Exit fullscreen mode