Inheritance
Understand how to create class hierarchies through inheritance in Dart.
What is it?
Inheritance is a mechanism in object-oriented programming that allows a class (subclass/child) to inherit properties and methods from another class (superclass/parent). It enables code reuse, establishes hierarchical relationships, and supports polymorphism.
Why does it exist?
Inheritance exists to:
- Reuse code across related classes
- Establish "is-a" relationships
- Enable polymorphism
- Support hierarchical classification
- Reduce code duplication
- Create extensible systems
Basic Inheritance
Extending a Class
// Base class (Superclass)
class Animal {
String name;
int age;
Animal(this.name, this.age);
void speak() {
print('$name makes a sound');
}
String get type => 'Animal';
void move() {
print('$name moves');
}
}
// Derived class (Subclass)
class Dog extends Animal {
String breed;
Dog(String name, int age, this.breed) : super(name, age);
// Override method
@override
void speak() {
print('$name barks');
}
// Override getter
@override
String get type => 'Dog';
// Additional method
void fetch() {
print('$name fetches the ball');
}
}
// Usage
var dog = Dog('Rex', 3, 'German Shepherd');
dog.speak(); // Rex barks (overridden)
dog.move(); // Rex moves (inherited)
dog.fetch(); // Rex fetches the ball (own method)
print(dog.type); // Dog (overridden)
print(dog.name); // Rex (inherited)
Calling Superclass Methods
class Vehicle {
String brand;
String model;
int year;
Vehicle(this.brand, this.model, this.year);
void start() {
print('$brand $model engine starting...');
}
void stop() {
print('$brand $model stopping...');
}
String get info => '$brand $model ($year)';
}
class Car extends Vehicle {
int doors;
String fuelType;
Car(String brand, String model, int year, this.doors, this.fuelType)
: super(brand, model, year);
@override
void start() {
super.start(); // Call parent method
print('Car is ready to drive');
}
@override
void stop() {
print('Applying brakes...');
super.stop(); // Call parent after
}
@override
String get info {
return '${super.info}, $doors doors, $fuelType';
}
void honk() {
print('Beep beep!');
}
}
// Usage
var car = Car('Toyota', 'Camry', 2020, 4, 'Gasoline');
car.start();
// Toyota Camry engine starting...
// Car is ready to drive
car.stop();
// Applying brakes...
// Toyota Camry stopping...
print(car.info); // Toyota Camry (2020), 4 doors, Gasoline
Inheritance Hierarchy
Multi-level Inheritance
// Base class
class Employee {
String name;
int id;
double salary;
Employee(this.name, this.id, this.salary);
void work() {
print('$name is working');
}
double calculateBonus() {
return salary * 0.1; // 10% bonus
}
String get position => 'Employee';
}
// Intermediate class
class Manager extends Employee {
List<Employee> team = [];
Manager(String name, int id, double salary) : super(name, id, salary);
void addEmployee(Employee employee) {
team.add(employee);
}
@override
void work() {
print('$name is managing the team');
}
@override
double calculateBonus() {
return salary * 0.2; // 20% bonus
}
@override
String get position => 'Manager';
}
// Derived class
class Director extends Manager {
String department;
Director(String name, int id, double salary, this.department)
: super(name, id, salary);
@override
void work() {
print('$name is directing the $department department');
}
@override
double calculateBonus() {
return salary * 0.3; // 30% bonus
}
@override
String get position => 'Director';
void approveBudget() {
print('$name approved budget for $department');
}
}
// Usage
var employee = Employee('Alice', 1, 50000);
var manager = Manager('Bob', 2, 80000);
var director = Director('Charlie', 3, 120000, 'Engineering');
print(employee.position); // Employee
print(manager.position); // Manager
print(director.position); // Director
print(employee.calculateBonus()); // 5000
print(manager.calculateBonus()); // 16000
print(director.calculateBonus()); // 36000
director.approveBudget(); // Charlie approved budget for Engineering
The super Keyword
Using Super
class Shape {
String color;
Shape(this.color);
void draw() {
print('Drawing a $color shape');
}
double get area => 0;
}
class Circle extends Shape {
double radius;
Circle(String color, this.radius) : super(color);
@override
void draw() {
super.draw(); // Call parent method
print('Circle with radius $radius');
}
@override
double get area => 3.14159 * radius * radius;
// Using super in methods
String getDescription() {
return '${super.toString()} - Circle($radius)';
}
}
class ColoredCircle extends Circle {
String borderColor;
ColoredCircle(String color, double radius, this.borderColor)
: super(color, radius);
@override
void draw() {
super.draw(); // Circle's draw
print('Border color: $borderColor');
}
// Multi-level super
String get fullDescription() {
return '${super.getDescription()} with border $borderColor';
}
}
// Usage
var circle = ColoredCircle('Red', 5, 'Black');
circle.draw();
// Drawing a Red shape
// Circle with radius 5
// Border color: Black
print(circle.fullDescription);
// Instance of 'ColoredCircle' - Circle(5.0) with border Black
Method Overriding
Overriding Methods
class Payment {
double amount;
String currency;
Payment(this.amount, this.currency);
void process() {
print('Processing payment of $amount $currency');
}
bool validate() {
return amount > 0;
}
String get status => 'Pending';
}
class CreditCardPayment extends Payment {
String cardNumber;
String expiryDate;
CreditCardPayment(
double amount,
String currency,
this.cardNumber,
this.expiryDate,
) : super(amount, currency);
@override
void process() {
if (!validate()) {
throw Exception('Invalid payment');
}
print('Processing credit card payment...');
print('Card: ****${cardNumber.substring(cardNumber.length - 4)}');
print('Amount: $amount $currency');
print('Payment successful!');
}
@override
bool validate() {
return super.validate() &&
cardNumber.length >= 16 &&
expiryDate.isNotEmpty;
}
@override
String get status => 'Completed';
}
class PayPalPayment extends Payment {
String email;
PayPalPayment(double amount, String currency, this.email)
: super(amount, currency);
@override
void process() {
print('Processing PayPal payment for $email');
print('Amount: $amount $currency');
print('Payment successful!');
}
@override
bool validate() {
return super.validate() && email.contains('@');
}
@override
String get status => 'Completed';
}
// Usage
void processPayment(Payment payment) {
print('Validating payment...');
if (payment.validate()) {
print('Payment is valid');
payment.process();
print('Status: ${payment.status}');
} else {
print('Invalid payment');
}
print('---');
}
var creditCard = CreditCardPayment(100, 'USD', '1234567890123456', '12/25');
var paypal = PayPalPayment(50, 'EUR', 'user@example.com');
processPayment(creditCard);
processPayment(paypal);
Inheritance and Constructors
Constructor Chaining
class Product {
final String id;
final String name;
final double price;
Product(this.id, this.name, this.price);
Product.basic(String name, double price)
: id = DateTime.now().millisecondsSinceEpoch.toString(),
name = name,
price = price;
Product.fromJson(Map<String, dynamic> json)
: id = json['id'] as String,
name = json['name'] as String,
price = json['price'] as double;
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'price': price,
};
}
class DigitalProduct extends Product {
final String downloadUrl;
final double fileSize;
DigitalProduct(
String id,
String name,
double price,
this.downloadUrl,
this.fileSize,
) : super(id, name, price);
DigitalProduct.basic(String name, double price, this.downloadUrl, this.fileSize)
: super.basic(name, price);
DigitalProduct.fromJson(Map<String, dynamic> json)
: downloadUrl = json['downloadUrl'] as String,
fileSize = json['fileSize'] as double,
super.fromJson(json);
@override
Map<String, dynamic> toJson() => {
...super.toJson(),
'downloadUrl': downloadUrl,
'fileSize': fileSize,
};
}
// Usage
var product1 = DigitalProduct('1', 'E-book', 9.99, 'https://...', 2.5);
var product2 = DigitalProduct.basic('Video Course', 49.99, 'https://...', 1024.0);
var product3 = DigitalProduct.fromJson({
'id': '3',
'name': 'Software',
'price': 99.99,
'downloadUrl': 'https://...',
'fileSize': 512.0,
});
print(product1.name); // E-book
print(product2.downloadUrl); // https://...
print(product3.toJson());
Polymorphism
Using Polymorphism
abstract class Employee {
String name;
Employee(this.name);
double calculateSalary();
String get role;
}
class Developer extends Employee {
double baseSalary;
int projectsCompleted;
Developer(String name, this.baseSalary, this.projectsCompleted)
: super(name);
@override
double calculateSalary() {
return baseSalary + (projectsCompleted * 500);
}
@override
String get role => 'Developer';
}
class Designer extends Employee {
double baseSalary;
int designsCreated;
Designer(String name, this.baseSalary, this.designsCreated)
: super(name);
@override
double calculateSalary() {
return baseSalary + (designsCreated * 300);
}
@override
String get role => 'Designer';
}
class Manager extends Employee {
double baseSalary;
List<Employee> team;
Manager(String name, this.baseSalary, this.team) : super(name);
@override
double calculateSalary() {
return baseSalary + (team.length * 1000);
}
@override
String get role => 'Manager';
}
// Polymorphic usage
void printEmployeeInfo(Employee employee) {
print('${employee.name} (${employee.role})');
print('Salary: \$${employee.calculateSalary().toStringAsFixed(2)}');
print('---');
}
void processEmployees(List<Employee> employees) {
double totalSalary = 0;
for (var employee in employees) {
printEmployeeInfo(employee);
totalSalary += employee.calculateSalary();
}
print('Total salary: \$${totalSalary.toStringAsFixed(2)}');
}
// Usage
var employees = [
Developer('Alice', 50000, 10),
Designer('Bob', 45000, 8),
Manager('Charlie', 60000, []),
];
processEmployees(employees);
Best Practices
Use Inheritance for "Is-A" Relationships
// Good: Car IS-A Vehicle
class Vehicle { /* ... */ }
class Car extends Vehicle { /* ... */ }
// Good: Dog IS-A Animal
class Animal { /* ... */ }
class Dog extends Animal { /* ... */ }
// Bad: Car HAS-A Engine (use composition instead)
class Car extends Engine { /* ... */ } // Wrong!
// Better: Composition
class Car {
Engine engine; // HAS-A relationship
}
Keep Inheritance Shallow
// Bad: Deep inheritance tree
// Animal -> Mammal -> Canine -> Dog -> GermanShepherd
// Good: Shallow inheritance
// Animal -> Dog (with breed as property)
class Animal { /* ... */ }
class Dog extends Animal {
String breed; // Breed is property, not subclass
}
Use @override Annotation
// Good: With @override
class Dog extends Animal {
@override
void speak() {
print('Woof!');
}
}
// Bad: Missing @override
class Cat extends Animal {
void speak() { // Should use @override
print('Meow!');
}
}
Common Mistakes
Constructor Without Super
Wrong:
class Animal {
String name;
Animal(this.name);
}
class Dog extends Animal {
String breed;
Dog(this.breed) : super(''); // Must call super
}
Correct:
class Dog extends Animal {
String breed;
Dog(String name, this.breed) : super(name);
}
Confusing IS-A with HAS-A
Wrong:
// Car IS-A Engine? No! Car HAS-A Engine
class Car extends Engine { /* ... */ }
Correct:
// Car HAS-A Engine
class Car {
final Engine engine;
Car(this.engine);
}
Summary
Inheritance is a fundamental OOP concept that enables code reuse and hierarchical relationships. Use it wisely for IS-A relationships, and prefer composition for HAS-A relationships.
Next Steps
Now that you understand inheritance, continue to:
Did You Know?
- Dart supports single inheritance only (one superclass)
- All classes inherit from Object
- Use
superto call parent methods @overrideis optional but recommended- Inheritance is transitive (grandchild inherits from grandparent)
- Constructors are not inherited
- You can override methods and getters/setters