Inheritance
Prerequisites: Constructors
Inheritance lets a class build on another class. The subclass gets the superclass's fields and methods and can add or change behavior.
Example
class Vehicle {
final int wheels;
Vehicle(this.wheels);
String describe() => 'A vehicle with $wheels wheels';
}
class Bicycle extends Vehicle {
Bicycle() : super(2);
@override
String describe() => '${super.describe()} that you pedal';
}
class Car extends Vehicle {
final String brand;
Car(this.brand) : super(4);
@override
String describe() => 'A $brand car';
}
void main() {
final List<Vehicle> vehicles = [Bicycle(), Car('Volvo')];
for (final vehicle in vehicles) {
print(vehicle.describe());
}
}
Expected output
A vehicle with 2 wheels that you pedal
A Volvo car
extends Vehiclemakes the class a subclass.: super(2)calls the superclass constructor.@overridemarks a method that replaces the superclass's version. It's optional to the compiler, but the recommended lints require it, and it catches typos.super.describe()calls the superclass's version.- A
List<Vehicle>can hold any subclass, and each element uses its owndescribe()— this is polymorphism.
Super parameters
When a subclass just passes values up, super parameters avoid repeating them:
class Animal {
final String name;
Animal(this.name);
}
class Cat extends Animal {
final bool indoor;
Cat(super.name, {this.indoor = true});
}
void main() {
final cat = Cat('Tom');
print('${cat.name}, indoor: ${cat.indoor}');
}
Tom, indoor: true
Abstract classes
An abstract class can't be created directly, and can declare methods that subclasses must implement:
abstract class Shape {
double area();
String describe() => 'Area: ${area().toStringAsFixed(2)}';
}
class Square extends Shape {
final double side;
Square(this.side);
@override
double area() => side * side;
}
void main() {
print(Square(3).describe());
}
Area: 9.00
Prefer composition
Deep inheritance chains become hard to follow. Dart code often shares behavior with interfaces and mixins instead — the next two lessons.