Home
Dart
OOP
Flutter
Contact

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:

notification.dart
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:

email_notification.dart
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_annotation.dart
@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.

⚠️ Inheritance Pitfall: The Fragile Base Class Problem When a base class changes, ALL subclasses are affected. If you modify a method's behavior in Notification, it cascades to EmailNotification, SMSNotification, and every other subclass.

Method Overriding

Use @override to customize inherited behavior:

push_notification.dart
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
📐 Composition vs Inheritance The Dart team recommends favoring composition over inheritance. If you find yourself overriding many methods in deep hierarchies, it's often a sign that composition (embedding objects) would be cleaner and more flexible.

Real-World Example: Notification System

main.dart
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

  • extends creates a subclass that inherits from a parent class
  • super calls the parent constructor
  • @override marks methods that replace parent behavior
  • Polymorphism allows treating subclass objects as their parent type
🚀 Next Step Continue to Abstraction & Interfaces.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← Encapsulation Next: Abstraction →