Compile-time vs Runtime Polymorphism
Compile-time vs Runtime Polymorphism
About
- Compile-time: Resolved during compilation (method overloading)
- Runtime: Determined at execution (method overriding)
- Dart’s Model: Primarily runtime polymorphism
- Performance: Different tradeoffs
Main Topics
-
Method Overloading
- Definition: Same name, different parameters
- Example:
// Not directly supported in Dart // Use named parameters instead: void log(String message, {int priority}) { // Implementation }
-
Method Overriding
-
Definition: Subclass redefines method
-
Example:
class Animal { void speak() => print('Sound'); } class Dog extends Animal { @override void speak() => print('Bark'); }
-
-
Static Dispatch
- Definition: Compile-time resolution
- Example:
// Dart uses runtime dispatch for instance methods // Final/private methods may be statically resolved
-
Dynamic Dispatch
- Definition: Runtime method lookup
- Example:
Animal myAnimal = Dog(); myAnimal.speak(); // Prints "Bark" (runtime decision)
-
Performance Impact
- Definition: Runtime costs
- Example:
// Virtual calls have small overhead // JIT may optimize frequent paths
How to Use
- Flexibility: Leverage runtime polymorphism
- Performance: Consider final methods
- Design: Prefer composition where appropriate
- Clarity: Use @override consistently
How It Works
- VTable: Virtual method table
- Lookup: Runtime type checking
- Optimization: JIT specialization
- Caching: Frequent call paths
Example:
abstract class Shape {
void draw(); // Abstract method
}
class Circle implements Shape {
@override
void draw() => print('Drawing circle');
}
void render(Shape s) {
s.draw(); // Dynamic dispatch
}
void main() {
render(Circle()); // Prints "Drawing circle"
}
Conclusion
Dart primarily uses runtime polymorphism through method overriding and interface implementation, enabling flexible code structures while maintaining clear type hierarchies. The lack of traditional method overloading is compensated by named parameters and optional arguments.