OOP: Inheritance & Method Overriding
Build class hierarchies with extends, super, and @override.
The extends Keyword
Inheritance is one of the four pillars of Object-Oriented Programming. It allows a class (called the subclass or child class) to inherit properties and methods from another class (called the superclass or parent class). This promotes code reuse and establishes a natural hierarchy between related types.
Think of inheritance as an "is-a" relationship: an EmailNotification is a Notification. A PushNotification is a Notification. By defining common behavior in the parent, every subclass automatically gains that functionality without duplicating code.
Here's how you define a base class and extend it:
class Notification {
String title;
String body;
DateTime timestamp;
Notification(this.title, this.body)
: timestamp = DateTime.now();
void send() {
print('Sending notification: $title');
}
String toString() => '[$title] $body';
}
Super Constructors
When a subclass is created, the parent class needs to be initialized too. Dart requires you to call the parent constructor using the super keyword. This ensures the parent's fields are properly set up before the child adds its own state.
You can pass required positional arguments or named arguments to super. If the parent has optional parameters with defaults, you can override them from the child. Here's how it works:
class EmailNotification extends Notification {
String recipientEmail;
bool hasReadReceipt;
EmailNotification(
String title,
String body,
this.recipientEmail, {
this.hasReadReceipt = false,
}) : super(title, body); // Pass to parent
@override
void send() {
print('📧 Sending email to $recipientEmail: $title');
}
}
Method Overriding
Method overriding lets a subclass provide its own implementation of a method defined in the parent class. This is how polymorphism works in practice — the same method call behaves differently depending on the actual type of the object at runtime.
The @override Annotation
Always use @override when overriding a method. This isn't just documentation — the Dart analyzer will warn you if you make a typo in the method name or if the parent class doesn't actually have that method:
@override
String toString() => 'Notification(to: ${recipient}, priority: ${priority})';
// Without @override, typos silently create new methods:
@override
String toStirng() => ...; // ⚠️ Analyzer warns
Multi-Level Inheritance
While Dart supports multi-level inheritance chains, keep them shallow. Deep hierarchies (3+ levels) become hard to maintain. The Dart team recommends favoring composition over inheritance when possible.
Notification, it cascades to EmailNotification, SMSNotification, and every other subclass.
Method Overriding
Use @override to customize inherited behavior:
class PushNotification extends Notification {
String deviceId;
int priority; // 1-5
PushNotification(
String title,
String body,
this.deviceId, {
this.priority = 3,
}) : super(title, body);
@override
void send() {
var urgency = priority >= 4 ? '🔴 HIGH' : '🟢 NORMAL';
print('📱 Push [$urgency] to $deviceId: $title');
}
}
When to Use Inheritance
Inheritance is powerful, but it's not always the right tool. Use it when:
- There's a clear is-a relationship (a dog is an animal, not a pet is an animal)
- Multiple subclasses share common behavior that belongs in one place
- You need polymorphism — treating different types uniformly through a common interface
Avoid inheritance when:
- The relationship is "has-a" (a car has an engine — use composition, not inheritance)
- You're creating deep class hierarchies (3+ levels) that become hard to maintain
- You only need code reuse without a type hierarchy — consider mixins or composition instead
Real-World Example: Notification System
void main() {
var notifications = <Notification>[
EmailNotification('Welcome!', 'Thanks for joining.', '[email protected]'),
PushNotification('New message', 'You have a new DM.', 'device_abc', priority: 5),
PushNotification('Update available', 'v2.1 is ready.', 'device_xyz'),
];
// Polymorphism: each notification sends differently
for (var n in notifications) {
print(n.toString());
n.send();
print('---');
}
}
Summary
extendscreates a subclass that inherits from a parent classsupercalls the parent constructor@overridemarks methods that replace parent behavior- Polymorphism allows treating subclass objects as their parent type