Modeling Polymorphism and Its Various Forms
Polymorphism
Polymorphism means “many forms” - the ability of objects to take on different forms depending on context.
- The same method name or operator can behave differently based on the object or arguments involved.
- Promotes flexibility, reusability, and cleaner code in OOP.
Think of polymorphism as a single interface with multiple behaviours.
Confusing polymorphism with inheritance: inheritance is the mechanism, polymorphism is the behaviour.
Forms of Polymorphism
- Compile-time (Static):
- Decided at compile time.
- Achieved by method overloading or operator overloading (Python supports operator overloading but not true method overloading).
- Runtime (Dynamic):
- Decided at runtime.
- Achieved by method overriding in subclasses.
- Static = overloading, decided early
- Dynamic = overriding, decided late.
Saying Python supports method overloading in the same way as Java: it does not.
Method Overloading (Static Polymorphism)
- Definition Reminder: Same method name, different parameter lists (different number or type of parameters).
- Return type alone is NOT enough to distinguish overloaded methods in Java, the compiler needs different parameters.
- Compile-time resolution: The method to execute is chosen at compile-time based on the arguments passed.
- Default parameters in Python: Since Python doesn’t support true overloading, default parameters and *args / **kwargs are often used to mimic it.
Python:
class MathOps:
def add(self, a, b=0):
return a + bJava:
class MathOps {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}- In Java: Stress that signature = method name + parameter list.
- In Python: Show how flexible arguments simulate overloading since Python only keeps the last defined method.
- Thinking Java can overload based only on different return types (not allowed).
- Forgetting that in Python, method redefinitions overwrite previous ones (no true overloading).
- Mixing up overloading (compile-time) with overriding (runtime).
Method Overriding (Dynamic Polymorphism)
Python:
class Animal:
def sound(self):
print("Animal makes a sound")
class Dog(Animal):
def sound(self):
print("Dog barks")
a = Dog()
a.sound() # Dog barksJava:
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}Overriding = “replace the parent’s version with your own.”
Forgetting to use @Override in Java, or mismatching the method signature, causing unintended overloading instead of overriding.
Advantages of Polymorphism
- Reusability: generic code can work across multiple subclasses.
- Flexibility: different objects share the same interface but act differently.
- Extensibility: new subclasses can be added without changing parent code.
- Simplification: reduces code duplication.
Always link polymorphism back to “same method, different behaviours.”
Writing advantages that belong to inheritance or encapsulation instead: keep them distinct.
Disadvantages / Limitations
- More runtime overhead (for dynamic polymorphism).
- Debugging can be harder with deep hierarchies.
- Overloading can lead to confusion about which method is called.
Mention at least one limitation in exams to show balanced understanding.
Saying polymorphism always makes code faster: dynamic polymorphism actually adds a slight performance cost.
Key takeaway:
Polymorphism is about designing methods that adapt behaviour depending on the object.
- Overloading = static (compile-time).
- Overriding = dynamic (runtime).