Inheritance in Dart
By Syncfusion®, Inc. · Last updated
How to build a child class on a parent with extends, override and use super, and when to build objects from parts instead, with aggregation and composition.
What Is Inheritance?
Inheritance = making a new class from an existing class. The new class gets everything the old one has, and adds more.
- Parent class (superclass) = the class you start from.
- Child class (subclass) = the new class built on top of it.
- The child inherits the parent's fields and methods.
- An "is-a" test tells you when to use it: a Student is a Person.
Simple lesson: inheritance lets you write shared code once, in the parent, and reuse it in every child.
A Real-World Example
Everyone in Green Valley School is a person with a name.
- A student is a person who also has a roll number.
- A teacher is a person who also has a subject.
- A class monitor is a student who also has a duty.
- Writing
nameandgreet()again in every class would be a waste. The parent holds them once.
Click a class to see what it owns and what it inherits:
Every Run box on this page runs on the course's Dart runner in your browser.
In Dart, every class you write already has a parent: Object. That is where toString() and hashCode come from.
What You Will Learn
In this lesson, you will learn:
- How to create a child class with
extends, and the kinds of inheritance. - How to override methods and use
super. - What has-a relationships are.
- Aggregation and composition, and how they differ.
- When to choose composition over inheritance.
The extends Keyword
Creating a Child Class
class Child extends Parent { ... }
A Student is a Person
class Person {
String name;
Person(this.name);
void greet() => print('Hello, I am $name.');
}
class Student extends Person {
int rollNo;
Student(String name, this.rollNo) : super(name);
void study() => print('$name (roll $rollNo) is studying.');
}
void main() {
var s = Student('Asha', 12);
s.greet();
s.study();
print(s.name);
}
Studentnever wrotegreet()orname, but it has both.: super(name)passesnameup to thePersonconstructor. More on this below.
Inherited Fields and Methods
What a child gets from its parent:
| Gets | Example |
|---|---|
| Public fields | name |
| Public methods | greet() |
| Getters and setters | get initials |
Private _ members, if in the same file | _secret |
What a child does not get:
- Constructors. Each class writes its own, and calls the parent's with
super. - Static members. Use them with the parent's name:
Person.count.
Simple lesson: extends copies the parent's abilities into the child, except constructors and statics.
Types of Inheritance
Single, Multi-Level and Hierarchical
| Type | Shape | Example |
|---|---|---|
| Single | one parent, one child | Person → Student |
| Multi-level | a chain | Person → Student → Monitor |
| Hierarchical | one parent, many children | Person → Student and Person → Teacher |
Multi-level and hierarchical together
class Person {
String name;
Person(this.name);
void greet() => print('Hello, I am $name.');
}
class Student extends Person {
Student(String name) : super(name);
void study() => print('$name is studying.');
}
class Monitor extends Student {
Monitor(String name) : super(name);
void takeAttendance() => print('$name is taking attendance.');
}
class Teacher extends Person {
Teacher(String name) : super(name);
void teach() => print('$name is teaching.');
}
void main() {
var m = Monitor('Kiran');
m.greet();
m.study();
m.takeAttendance();
Teacher('Mrs. Iyer').greet();
print(m is Person);
print(m is Teacher);
}
A Monitor has all three methods. And m is Person is true, because a monitor is a student, and a student is a person.
Multiple Inheritance: Not in Dart
Multiple inheritance = one class with two parents: class SportsCaptain extends Student, Athlete.
- Dart does not allow this. A class has exactly one parent.
- Two parents could both have a
play()method. Which one should run? Dart avoids that confusion. Instead, Dart gives you two tools:
- interfaces with
implements(see Polymorphism) - mixins with
with(see Mixins)
- interfaces with
class SportsCaptain extends Student, Athlete {}
// Error: Each class definition can have at most one extends clause.
Simple lesson: a Dart class has one parent, but that parent can have its own parent, and so on.
Overriding Methods
Using @override
Override = a child writes its own version of a method it inherited.
- Same name, same parameters, a new body.
- Put
@overrideabove it. It tells readers (and Dart) "this replaces a parent method". - If you spell the name wrong,
@overridemakes Dart warn you.
Each animal speaks its own way
class Animal {
String name;
Animal(this.name);
void speak() => print('$name makes a sound.');
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
void speak() => print('$name says Woof!');
}
class Cat extends Animal {
Cat(String name) : super(name);
}
void main() {
Dog('Tommy').speak();
Cat('Billu').speak();
}
Dog overrides speak(). Cat does not, so it uses the parent's version.
Overriding toString()
Every class inherits toString() from Object. Override it to print something useful:
A friendly print for a student
class Student {
String name;
int marks;
Student(this.name, this.marks);
@override
String toString() => 'Student($name, $marks)';
}
void main() {
var s = Student('Ravi', 75);
print(s);
print([s, Student('Priya', 85)]);
}
@override is not required to make overriding work. But always write it.
It documents your intent, and it catches spelling mistakes like toSting().
Simple lesson: override when the child must do the job differently, and mark it with @override.
The super Keyword
Calling the Parent's Method
super = "the parent's version". Use it inside a child to reach what you replaced.
super.describe() runs the parent part first
- Without
super., callingdescribe()insidedescribe()would call itself forever. super.lets the child add to the parent's work, instead of replacing it all.
Calling the Parent's Constructor
A child must build its parent part first. You pass the values with super(...):
super in the initializer list
class Person {
String name;
Person(this.name) {
print('Person part built for $name');
}
}
class Teacher extends Person {
String subject;
Teacher(String name, this.subject) : super(name) {
print('Teacher part built: $subject');
}
}
void main() {
var t = Teacher('Mrs. Iyer', 'Science');
print('${t.name} teaches ${t.subject}');
}
The parent's constructor body runs before the child's body.
Super Parameters
Super parameter = super.name in the child's parameter list. It passes the value straight to the parent. Shorter, same meaning.
super.name does the passing for you
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
class Student extends Person {
final String house;
Student(super.name, super.age, this.house);
@override
String toString() => '$name, $age, $house house';
}
void main() {
print(Student('Meena', 13, 'Blue'));
}
| Long way | Short way |
|---|---|
Student(String name, int age, this.house) : super(name, age); | Student(super.name, super.age, this.house); |
Simple lesson: super.method() reuses the parent's method, and super(...) or super.x builds the parent part.
Designing Good Hierarchies
- Put shared fields and methods in the parent. Put special ones in the child.
- Keep chains short. Two or three levels is usually enough.
- Use the "is-a" test. A
Caris not anEngine, soCar extends Engineis wrong. - When the test is "has-a" (a car has an engine), use a field instead, as shown below.
Do not inherit just to borrow one method.
If Library extends Student only to reuse printName(), every library would also get rollNo and study(). That makes no sense.
Easy way to remember
extends= "is a kind of".@override= "I do this my own way".super.x()= "do it the parent's way too".super.namein the constructor = "pass this up to the parent".
Simple lesson: inheritance is for real "is-a" families, kept short and simple.
What Are Aggregation and Composition?
Both are ways to build a big object out of smaller objects. They are "has-a" relationships.
- Has-a relationship = one object keeps another object in a field. A school has classrooms.
- Aggregation = the parts are shared. They were made outside, and they live on if the whole goes away.
- Composition = the parts are owned. The whole makes them, and they go away with it.
Simple lesson: aggregation borrows parts, and composition owns its parts.
A Real-World Example
Look at Green Valley School.
- The school has classrooms. Rooms 7A and 7B are built inside the school. Close the school, and the rooms are gone too. That is composition.
- The school has teachers. Mrs. Iyer was a person before she joined, and she still is if the school closes. That is aggregation.
Close the school and watch which parts go with it:
Dart does not have special keywords for these ideas. You show them by where an object is made, and who keeps it.
Has-a Relationships
Is-a (from above) and has-a are different questions:
| Question | Answer | Tool |
|---|---|---|
| Is a Student a Person? | yes | extends |
| Does a Student have a Bag? | yes | a field: Bag bag; |
| Is a Car an Engine? | no | do not extend |
| Does a Car have an Engine? | yes | a field: Engine engine; |
A student has a bag
class Bag {
final String colour;
final List<String> books = [];
Bag(this.colour);
}
class Student {
final String name;
final Bag bag;
Student(this.name, this.bag);
void pack(String book) {
bag.books.add(book);
print('$name packed $book in the ${bag.colour} bag');
}
}
void main() {
var s = Student('Asha', Bag('blue'));
s.pack('Maths');
s.pack('Science');
print(s.bag.books);
}
Simple lesson: if "X has a Y" sounds right, give X a field of type Y.
Aggregation
Aggregation = the whole holds a link to parts that exist on their own.
- The parts are usually passed in from outside (as constructor or method arguments).
- The same part can be shared by many wholes.
- Removing the whole does not remove the parts.
Two clubs share the same student
class Student {
final String name;
Student(this.name);
}
class Club {
final String title;
final List<Student> members = [];
Club(this.title);
void join(Student s) => members.add(s);
void show() => print('$title: ${members.map((m) => m.name).join(', ')}');
}
void main() {
var asha = Student('Asha');
var ravi = Student('Ravi');
var chess = Club('Chess')..join(asha)..join(ravi);
var music = Club('Music')..join(asha);
chess.show();
music.show();
chess.members.clear();
print('Chess club closed. Asha is still ${asha.name}.');
music.show();
}
ashawas made outside both clubs.- Clearing the chess club did not touch her. She is still in the music club.
Simple lesson: in aggregation, the whole only points to parts that someone else made.
Composition
Composition = the whole creates and owns its parts.
- The parts are made inside the whole, often in its constructor.
- Nobody else holds them. They are private to the whole.
- When the whole is gone, the parts are gone too.
A timetable owns its periods
class Period {
final String subject;
final String time;
Period(this.subject, this.time);
}
class Timetable {
final String className;
final List<Period> _periods = [];
Timetable(this.className) {
_periods.add(Period('Maths', '9:00'));
_periods.add(Period('Science', '9:45'));
_periods.add(Period('English', '10:30'));
}
void show() {
print('Timetable for $className');
for (var p in _periods) {
print(' ${p.time} ${p.subject}');
}
}
}
void main() {
var t = Timetable('7A');
t.show();
}
- The
Periodobjects are made insideTimetable. _periodsis private, so no outside code can take them away.- A period for 7A has no meaning without 7A's timetable.
A quick test: who called the constructor of the part?
- The whole called it: composition.
- Someone else called it, and passed the part in: aggregation.
Simple lesson: in composition, the whole builds its parts and keeps them to itself.
Comparing the Two
| Aggregation | Composition | |
|---|---|---|
| Ownership | shared, the whole only links to it | owned by the whole |
| Who makes the part? | outside code | the whole itself |
| Lifetime | the part lives on alone | the part ends with the whole |
| Shared by many wholes? | yes | no |
| School example | school has teachers | school has classrooms |
| Other example | a playlist has songs | a house has rooms |
Both in one program:
A playlist borrows songs, a house owns rooms
class Song {
final String title;
Song(this.title);
}
class Playlist {
final String name;
final List<Song> songs;
Playlist(this.name, this.songs);
}
class Room {
final String use;
Room(this.use);
}
class House {
final List<Room> _rooms = [Room('kitchen'), Room('bedroom')];
int get roomCount => _rooms.length;
}
void main() {
var anthem = Song('Jana Gana Mana');
var morning = Playlist('Assembly', [anthem]);
var sports = Playlist('Sports Day', [anthem]);
print('${morning.name} and ${sports.name} share ${anthem.title}');
var home = House();
print('The house made its own ${home.roomCount} rooms');
}
Simple lesson: ask "can the part live alone?". Yes means aggregation. No means composition.
Inheritance or Composition?
Sometimes both could work. Which is better?
A phone built from parts, not from a parent
class Camera {
void click() => print('Photo taken');
}
class Speaker {
void play(String song) => print('Playing $song');
}
class Phone {
final Camera _camera = Camera();
final Speaker _speaker = Speaker();
void takePhoto() => _camera.click();
void playSong(String song) => _speaker.play(song);
}
void main() {
var p = Phone();
p.takePhoto();
p.playSong('Jana Gana Mana');
}
- A phone is not a camera. It has a camera. So composition fits.
- With
extends, a phone could only have one parent. With fields, it can have many parts. - Each part can be changed or tested on its own.
| Choose | When |
|---|---|
Inheritance (extends) | a true "is-a", and the child really is a special kind of the parent |
| Composition (fields) | a "has-a", or you want to mix many abilities |
A common beginner mistake is class Phone extends Camera. It compiles, but now a phone is a camera, with every camera method on it. Later changes to Camera can break Phone in surprising ways.
Easy way to remember
- is-a: inheritance.
- has-a, borrowed: aggregation (passed in, shared).
- has-a, owned: composition (made inside, private).
- When in doubt, prefer composition.
Simple lesson: prefer building from parts, and use inheritance only for real "is-a" families.
TL;DR
class Child extends Parentinherits the parent's fields and methods. One parent only.- Override with
@override.super.m()runs the parent's version.super.namepasses values up. - Has-a = one object keeps another in a field.
- Aggregation: parts made outside and shared. Composition: parts made inside and owned.
- Use inheritance for is-a, and prefer composition for has-a.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
class A extends B, C | A class can have two parents | One parent only | A compile error. |
Forgetting : super(name) | The parent builds itself | Pass the parent's required values | A compile error: the parent needs arguments. |
Calling describe() inside describe() | It reaches the parent | Write super.describe() | It calls itself forever: Stack Overflow. |
Leaving out @override | It is only decoration | It catches spelling mistakes | A typo makes a new method silently. |
Car extends Engine | Inherit to reuse code | Use "is-a". A car has an engine | A confusing class design. |
| Expecting constructors to be inherited | Children get every member | Each class writes its own constructor | A compile error when you call it. |
class Car extends Engine | Inherit to reuse | A car has an engine: use a field | Odd methods appear on Car. |
| A public list of owned parts | Public is simpler | Keep owned parts private: _periods | Outside code removes parts the whole needs. |
| Making shared parts inside the whole | Every whole needs its own copy | Pass shared parts in | Two "Ashas" that should be one person. |
Thinking Dart has aggregate or compose keywords | They are language features | They are design ideas, built with fields | Confusion when reading code. |
Deep extends chains to collect abilities | More parents, more power | Combine parts with fields | Fragile, hard-to-change code. |
Check Yourself
Exercises
What to Remember
extendsmakes a child class that reuses the parent's code.- Override with
@override, and reach the parent withsuper. - A has-a relationship is a field that holds another object.
- Aggregation shares parts, and composition owns them.
- Keep inheritance for true is-a families, and build everything else from parts.
Next: Polymorphism. You will write plans that classes must follow, and see one call do different things for different objects.