Introduction

Hello, learner! In today's exciting chapter, we will unravel Polymorphism, a prominent feature of Object-Oriented Programming (OOP). Specifically, we will study its role in maintaining backward compatibility while introducing new features. Think of it as a software update that introduces new functions without breaking the older functionality — ingenious, isn't it?

Understanding Polymorphism

Polymorphism, a principle that derives from the Greek words 'poly' (many) and 'morphism' (forms), enables a variable or method to assume multiple roles — to embody various behaviors or functions determined by its data type or class.

Consider a class Bird with a method canFly(). If we create subclasses like Sparrow, Penguin, and Ostrich, we can override the canFly() method for certain subclasses. This demonstrates polymorphism in action.

class Bird {  // Superclass
    canFly(): string {
        return "Unknown";
    }
}

class Sparrow extends Bird {  // Subclass
    canFly(): string {
        return "Yes, I can fly!";
    }
}

class Penguin extends Bird {  // Subclass
    canFly(): string {
        return "No, I prefer swimming.";
    }
}

const sparrow = new Sparrow();
const penguin = new Penguin();
console.log("Sparrow says:", sparrow.canFly());  // Output: "Yes, I can fly!"
console.log("Penguin says:", penguin.canFly());  // Output: "No, I prefer swimming."

The Bird example demonstrates runtime polymorphism, where the canFly method behaves differently depending on the object type (Sparrow or Penguin). The superclass Bird provides a default implementation of canFly as "Unknown". This acts as a generic behavior for birds when no specific information is available. Subclasses override this method to provide species-specific behaviors. For example, the Sparrow class overrides canFly to return "Yes, I can fly!", while the Penguin class returns "No, I prefer swimming.". This ability to modify behavior while maintaining a shared interface (canFly) is the essence of polymorphism.

Polymorphism for Backward Compatibility

When adding new features, which introduce new behaviors to some components, polymorphism ensures that the existing parts function as before, thereby retaining backward compatibility. In complex cases, we maintain an older version of the method in the superclass for legacy support while offering newer functionalities in subclasses.

Take, for instance, a MathOperations class with a multiply() method that accepts two parameters. To support the multiplication of three numbers, we design a subclass, ExtendedMathOperations, and include a new multiply() method in it, ensuring backward compatibility.

class MathOperations {  // Superclass
    multiply(a: number, b: number): number {
        return a * b;
    }
}

class ExtendedMathOperations extends MathOperations {  // Subclass
    multiply(a: number, b: number, c: number = 1): number {
        return a * b * c;
    }
}

const mathOps = new MathOperations();
const extendedMathOps = new ExtendedMathOperations();
console.log(mathOps.multiply(2, 3));  // Output: 6
console.log(extendedMathOps.multiply(2, 3));  // Output: 6, keeping backward compatibility
console.log(extendedMathOps.multiply(2, 3, 4));  // Output: 24
Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal