88. Replace conditional with polymorphism#

Instead of relying on long chains of conditionals, we can use this principle to elegantly dispatch behavior based on the object’s type. The term ‘replace conditional with polymorphism’ was popularized by Martin Fowlers book, Refactoring’. It’s aptly named because we’re taking code that uses conditionals and rewrite it so that it instead uses polymorphism.

Key point

Using polymorphism can eliminate the need for explicit conditionals in your code, leading to more readable and maintainable solutions.

To metaphorically visualize this chapter’s concept, think of polymorphism as a symphony orchestra. Instead of one musician trying to play every instrument based on a list of conditions, each musician specializes in one instrument. When a sequence of notes is given to a violinist the piece is played on a violin. But if the same sequence of notes is given to the pianist it is played on a piano. In the same vein, polymorphism ensures the right code executes without the explicit need for conditions.

../_images/cover-replace-conditional-with-polymorphism.jpg

Fig. 88.1 Replacing conditionals with polymorphism is like moving from a single person playing every instrument to an orchestra where each person specializes in one instrument.#

Imagine you’re building a chess game. In chess, different pieces move in different patterns. For instance, a ‘Knight’ might move in an ‘L’ shape while a ‘Bishop’ might move diagonally. The naive way to code this might involve a lot of conditionals:

enum PieceType { Knight, Bishop, Pawn } // Etc...
class Piece
{
    public PieceType Type { get; set; }

    public void Move()
    {
        if (Type == PieceType.Knight)
            Console.WriteLine("Can move in L shape.");
        else if (Type == PieceType.Bishop)
            Console.WriteLine("Can move diagonally.");

        // ... etc
    }
}

The above approach may seem straightforward at first sight, but it becomes cumbersome and less maintainable as the number of piece types increases. Moreover, every time a new type is introduced, the Move method must be modified. This last point might not be relevant in the chess example but it certainly is valid in scenarios where the requirements aren’t completely known.

An elegant solution is to instead use polymorphism to delegate the responsibility of moving to the specific piece types.

Let’s refactor this to instead use polymorphism. The idea is to make each piece its own class with its own implementation of the instance method Move. We then make it possible to use instances of these classes polymorphically by letting them implement the same interface or inherit from the same base class. In this example we’re going to use an interface. Have a look at the refactored code that instead uses polymorphism:

interface IPiece
{
    void Move();
}
class Knight : IPiece
{
    public void Move()
        => Console.WriteLine("Can move in L shape.");
}
class Bishop : IPiece
{
    public void Move()
        => Console.WriteLine("Can move diagonally.");
}

With this setup, there’s no need for any conditionals. When you call the Move method on a piece (IPiece), the correct implementation for that piece type is automatically chosen. Revisit the chapters on subtype polymorphism and dynamic dispatch if this doesn’t make sense.

IPiece piece = new Knight();
piece.Move(); // No conditionals needed!
Can move in L shape.

Tip

When you find yourself using conditionals based on an object’s data type or properties, consider if polymorphism might be a better approach.

See also

In later chapters we’ll explore designs (such as strategy pattern) and principles (such as composition over inheritance) that take this basic idea even further.

Remember, a key goal in object-oriented programming as well as programming in general, is clarity and maintainability. By leveraging principles like polymorphism, you pave the way for clearer, more maintainable software