Polymorphism
How can one variable hold many different types of objects? Master Polymorphism, the 'shapeshifting' power of OOP.
The word "Polymorphism" comes from Greek, meaning "Many Shapes." In Low-Level Design, it is one of the most powerful tools for writing clean, flexible code. It allows you to treat different objects as if they were the same thing.
The Essentials
The "Shapeshifting" guide:
- Many Shapes: One interface or parent reference can point to many different child implementations.
- Compile-time vs Runtime: You decide the "Reference" at compile-time, but the computer decides the "Behavior" at runtime.
- Flexibility: You can add new child types without changing the code that uses the parent.
- Generalization: Instead of writing code for
GelPenandBallPenseparately, you write code for aPen.
The Remote Analogy (Revisited)
Think back to our Remote (Reference) and TV (Object) analogy.
- The Standard Remote: Imagine a "Universal Remote" labeled Animal.
- The Different TVs: You can point this remote at a Dog, a Cat, or a Lion.
- The Same Button: When you press the "Speak" button on the remote, the Dog will bark, the Cat will meow, and the Lion will roar.
The remote doesn't care what kind of animal it is. It only cares that the object it's pointing to is an "Animal" and knows how to "Speak."
Polymorphism in Action: The Shapeshifting Problem
Without polymorphism, you must check the type of every object manually. Adding a new type requires "surgery" on your logic.
if (user.type == "Student") { .. }
else if (user.type == "Mentor") { .. }
}
This code is rigid and breaks whenever you add a new role to the system.
With polymorphism, the code only knows about the parent User. Each child handles its own unique behavior.
user.login();
}
// New user? Just add a class.
// The loop stays the same!
The system is extensible. You can add 100 new user types without touching the main logic.
Code Implementation: Treating Many as One
Here is how you implement polymorphic behavior using a shared parent reference:
abstract class Animal {
abstract speak(): void;
}
class Dog extends Animal {
speak() { console.log("Barking!"); }
}
class Cat extends Animal {
speak() { console.log("Meowing!"); }
}
// Treat a list of many types as one list of "Animals"
const zoo: Animal[] = [new Dog(), new Cat(), new Dog()];
zoo.forEach(animal => {
animal.speak(); // Each responds in its own shape!
});Why It Matters: Avoiding the "If/Else" Trap
Without polymorphism, your code would look like this:
- "If user is Student, do X."
- "If user is Instructor, do Y."
- "If user is Mentor, do Z."
Every time you add a new user type, you have to find every if/else block in your entire app and update it. This is "Surgery" on your codebase.
With polymorphism, you just create a new class. The existing for loop doesn't change at all. It just works.
Now that we know the power of taking many forms, let's look at how we specialize behavior through Method Overriding.
Practice what you just read.