DEV Community

Diallo Mamadou Pathé
Diallo Mamadou Pathé

Posted on

Refactoring with Polymorphism: Say Goodbye to Complex Conditionals

Polymorphism banner

Introduction

Clarity and maintainability are pillars of quality code. Yet, we often find ourselves dealing with complex conditional structures - a series of if, else if, or switch blocks that span multiple lines or, worse, multiple screens. These constructs can quickly become a maintenance nightmare, especially when new conditions are added over time.

Fortunately, object-oriented programming offers us an elegant solution: polymorphism. In this article, we'll explore how this technique can transform complex code into something much more readable and scalable.

The Problem with Conditional Structures

Let's look at a concrete example from an animal management application:

enum FoodType {
    MEAT, VEGETABLES, FISH
}

class Animal {
    FoodType type;
    String name;
    double hungryLevel;

    void eat() {
        if(type == FoodType.MEAT) {
            hungryLevel -= 1;
        } else if(type == FoodType.VEGETABLES) {
            hungryLevel -= 0.5;
        } else if(type == FoodType.FISH) {
            hungryLevel -= 0.8;
        }
        // What if we add new food types?
    }
}
Enter fullscreen mode Exit fullscreen mode

This code presents several issues:

  1. Violation of the Open/Closed Principle: To add a new food type, we must modify the eat() method.
  2. Maintenance difficulty: The more food types we add, the longer the method becomes.
  3. Risk of errors: Complex conditional structures are prone to oversights and bugs.
  4. Potential duplication: If the same logic is required elsewhere, we risk duplicating these conditions.

The Solution: Polymorphism

Polymorphism allows different classes to implement a common method, each in its own way. Here's how to refactor our example:

abstract class FoodType {
    abstract double getSatiation();
}

class Meat extends FoodType {
    @Override
    double getSatiation() {
        return 1.0;
    }
}

class Vegetables extends FoodType {
    @Override
    double getSatiation() {
        return 0.5;
    }
}

class Fish extends FoodType {
    @Override
    double getSatiation() {
        return 0.8;
    }
}

class Animal {
    FoodType foodType;
    String name;
    double hungryLevel;

    void eat() {
        hungryLevel -= foodType.getSatiation();
    }
}
Enter fullscreen mode Exit fullscreen mode

The Benefits of Polymorphism

1. Adherence to the Open/Closed Principle

The Open/Closed Principle, one of the SOLID principles, states that software entities should be open for extension but closed for modification.

With our refactoring, adding a new food type is as simple as creating a new class that extends FoodType:

class Insects extends FoodType {
    @Override
    double getSatiation() {
        return 0.3;
    }
}
Enter fullscreen mode Exit fullscreen mode

No modifications are needed in the Animal class or other FoodType classes.

2. More Readable Code

The eat() method is now reduced to a single elegant line. It clearly expresses the intent without getting lost in implementation details.

3. Improved Encapsulation

Each food type encapsulates its own satiation logic. If we want to change how vegetables satiate, we only need to modify the Vegetables class.

4. Ease of Testing

Testing each food type becomes trivial. We can write unit tests for each implementation of FoodType without worrying about the Animal class.

@Test
void vegetablesSatiationIsFivetenths() {
    Vegetables vegetables = new Vegetables();
    assertEquals(0.5, vegetables.getSatiation());
}
Enter fullscreen mode Exit fullscreen mode

5. Extensibility

We can easily add new behaviors to our class hierarchy without disrupting existing code:

abstract class FoodType {
    abstract double getSatiation();

    // New behavior
    abstract boolean isProcessed();
}
Enter fullscreen mode Exit fullscreen mode

Practical Application in the Real World

Polymorphism isn't just an academic concept; it's used daily in software development:

  • UI Components: Frameworks like React or Flutter use polymorphism so different components can be rendered consistently.
  • Payment Strategies: E-commerce applications often use polymorphism to handle different payment methods.
  • File Format Parsers: Applications that process multiple file formats use polymorphism to encapsulate different parsing logics.

Conclusion

Replacing complex conditional structures with polymorphism is one of the most powerful refactoring techniques. It transforms difficult-to-maintain code into a well-organized set of classes that adhere to SOLID principles.

The next time you find yourself writing a long series of conditions, take a moment to consider if polymorphism might offer a more elegant solution. Your future self (and colleagues) will thank you!


Further Reading

Top comments (0)