Ahmet Balaman LogoAhmet Balaman

Dart Inheritance and Polymorphism - Parent-Child Relationship

personAhmet Balaman
calendar_today
DartOOPInheritancePolymorphismFlutter

My Struggle with Inheritance and Polymorphism

Today I touched the heart of object-oriented programming (OOP) in Dart: Inheritance and Polymorphism. Although it seemed a bit complex at first, it started to make sense as I worked through examples.

Live Demo: Inheritance and Polymorphism

Try inheritance and polymorphism concepts interactively:

What is Inheritance?

I can explain inheritance in its simplest form like this: Parent-child relationship. The parent's features pass to the child. In Dart, we call this extends.

SuperClass and SubClass

  • SuperClass: Upper class, parent class
  • SubClass: Lower class, child class

The child (SubClass) gets features from its parent (SuperClass) but the parent can't get from the child. It's a one-way relationship.

My First Inheritance Example: House Family

class House {
  late int windowCount;
  
  House(this.windowCount);
}

class Palace extends House {
  int? towerCount;
  
  Palace(this.towerCount, int windowCount) : super(windowCount);
}

class Villa extends House {
  bool? hasGarage;
  
  Villa(this.hasGarage, int windowCount) : super(windowCount);
}

void main() {
  var house1 = House(4);
  print("Normal house: ${house1.windowCount} windows");
  
  var palace1 = Palace(3, 50);
  print("Palace: ${palace1.towerCount} towers, ${palace1.windowCount} windows");
  
  var villa1 = Villa(true, 12);
  print("Villa: Has garage? ${villa1.hasGarage}, ${villa1.windowCount} windows");
}

Here's what I noticed: Palace and Villa classes derive from the House class. Both have windowCount because they get it from the parent (House). But each has its own unique features (tower count, garage).

The Super Keyword

super() calls the parent's constructor. We're saying "parent, here are the parameters needed for your features".

@override - Child Can Change Parent's Method

The override topic is very interesting. The child may not like a method from the parent and can write its own version.

Animal Sounds Example

I had a lot of fun with this example:

class Animal {
  void makeSound() {
    print("No sound");
  }
}

class Mammal extends Animal {}

class Cat extends Mammal {
  @override
  void makeSound() {
    print("Meow");
  }
}

class Dog extends Mammal {
  @override
  void makeSound() {
    print("Woof woof");
  }
}

class Fox extends Mammal {
  // Fox has no sound, will take from parent
}

void main() {
  var cat1 = Cat();
  var dog1 = Dog();
  var fox1 = Fox();
  var animal1 = Animal();
  
  cat1.makeSound();    // Meow
  dog1.makeSound();    // Woof woof
  fox1.makeSound();    // No sound (got from parent)
  animal1.makeSound(); // No sound
}

The Logic of Override

Cat and Dog defined their own sounds (overrode them). But we didn't define a special sound for Fox, so it used the parent class's method.

Dart works like this:

  1. First looks at its own class, is there a method?
  2. If not, looks at the class above
  3. If not there either, looks at the one above that
  4. If it's not even at the top, gives an error

Polymorphism - I'm a Bit Confused

I must say I didn't fully understand the polymorphism topic. But here's what I grasped: We can show the child's behavior in the parent's type.

Animal h = Cat();
h.makeSound(); // Prints "Meow"

Here the h variable is of type Animal but is actually a Cat object. That's why when we call it, it makes the Cat's sound.

Why Do We Use It?

From what I researched, it's useful in these situations:

  • Keeping different subclasses in the same list in list structures
  • Providing flexibility when sending parameters to functions
void makeAnimalSound(Animal animal) {
  animal.makeSound();
}

void main() {
  makeAnimalSound(Cat());    // Meow
  makeAnimalSound(Dog());    // Woof woof
  makeAnimalSound(Fox());    // No sound
}

The function takes a parameter of type Animal but we can send it Cat, Dog, or Fox. This is polymorphism.

Type Checking and Casting

We use is to check what type an object is:

var palace1 = Palace(3, 50);

if (palace1 is Palace) {
  print("This is a palace");
}

if (palace1 is House) {
  print("This is also a house"); // This works too!
}

Upcasting and Downcasting

When I first heard these names I thought "what's the connection" but I understood the logic:

Upcasting: Converting child to parent (happens automatically)

House house = palace1; // Converted Palace to House type

Downcasting: Converting parent to child (done forcefully)

House house1 = House(10);
Villa newVilla = house1 as Villa; // Forcefully converted to Villa

Warning: Downcasting is risky! If house1 isn't really a Villa, you'll get an error.

What I Learned Today

  • Inheritance prevents code repetition
  • @override allows us to change methods from parent
  • Polymorphism provides flexibility (didn't fully understand though)
  • is allows type checking
  • Upcasting and Downcasting allow transitions between types

I need more practice to better understand the polymorphism topic. Tomorrow I'll look at Interfaces and Abstract classes.

Where Do We Use It in Flutter?

Widgets in Flutter also use inheritance:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

StatelessWidget is our SuperClass, MyWidget is the SubClass.


For questions and suggestions:

See you in the next post!