31
🛠Refactoring: Replacing Conditional with Polymorphism
public class Bird
{
// whatever properties do birds have
public BirdType Type { get; set; }
}
public enum BirdType
{
African,
American,
European
}
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
}
}
}
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
}
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?
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();
}
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
}
}
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
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?)
- 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
31