Popular Searches
Popular Course Categories
Popular Courses

Polymorphism and Method Overriding

Polymorphism and Method Overriding

Object-Oriented Programming in Dart

 

Polymorphism and Method Overriding in Dart – Detailed Notes

 


    Polymorphism and method overriding are important Object-Oriented Programming
    concepts in Dart. They allow a program to work with objects of different classes through a common interface while
    still executing the appropriate child-class behavior.
 

 


    JustAcademy's Flutter curriculum includes Dart OOP concepts such as classes, objects, constructors,
    inheritance, polymorphism, and abstraction as part of Dart Programming Fundamentals.
   
      Explore JustAcademy's Flutter Training
   
.
    :contentReference[oaicite:0]{index=0}
 

 

1. What is Polymorphism?

 


    The word polymorphism comes from two words:
 

 


       
  • Poly – many

  •    
  • Morph – forms

  •  

 


    Therefore, polymorphism means "many forms." In object-oriented programming, polymorphism allows
    the same operation or interface to behave differently depending on the actual object involved.
 

 


    For example, different animals can have a sound() method, but each animal can implement that method
    differently.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

 


    The method name is the same, sound(), but its behavior changes according to the object.
 

 

2. What is Method Overriding?

 


    Method overriding occurs when a child class provides its own implementation of a method that it
    inherits from a parent class.
 

 


    The child method normally has the same name and a compatible method signature as the inherited method.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

 


    Here, Animal provides the original sound() method, while Dog overrides it.
 

 

3. Relationship Between Polymorphism and Method Overriding

 


    Method overriding is one of the main mechanisms used to achieve runtime polymorphic behavior in an inheritance
    hierarchy.
 

 

Parent Class
    |
    | inherited method
    v
Child Class
    |
    | overrides method
    v
Different implementation

 

For example:

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog bark');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meow');
  }
}

 


    Both Dog and Cat have the same method name, but the behavior is different.
 

 

4. Basic Method Overriding Example

 

class Parent {
  void showMessage() {
    print('Message from Parent');
  }
}

class Child extends Parent {
  @override
  void showMessage() {
    print('Message from Child');
  }
}

void main() {
  Child child = Child();

  child.showMessage();
}

 

Output:

 

Message from Child

 


    The child version is executed because Child overrides the inherited method.
 

 

5. The @override Annotation

 


    Dart provides the @override annotation to indicate that a member is intentionally overriding an
    inherited member.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog bark');
  }
}

 


    Using @override improves readability and allows Dart's analyzer to help identify mistakes in
    overriding inherited members.
 

 

6. Why Use Method Overriding?

 

Method overriding is useful when a child class needs behavior that is more specific than the parent behavior.

 


       
  • To customize inherited behavior.

  •    
  • To provide specialized implementations.

  •    
  • To support polymorphism.

  •    
  • To create flexible application designs.

  •    
  • To avoid duplicating unrelated class structures.

  •    
  • To allow different subclasses to respond differently to the same method call.

  •  

 

7. Simple Real-World Example: Animals

 


    Suppose an application needs to represent different animals. All animals have a sound, but each animal produces
    a different sound.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog says Woof');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat says Meow');
  }
}

class Cow extends Animal {
  @override
  void sound() {
    print('Cow says Moo');
  }
}

void main() {
  Dog dog = Dog();
  Cat cat = Cat();
  Cow cow = Cow();

  dog.sound();
  cat.sound();
  cow.sound();
}

 

Output:

 

Dog says Woof
Cat says Meow
Cow says Moo

 

8. Runtime Polymorphism

 


    Runtime polymorphism occurs when a parent-type variable refers to a child object and the overridden implementation
    associated with the actual object is executed.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

void main() {
  Animal animal = Dog();

  animal.sound();
}

 

Output:

 

Dog barks

 


    The variable animal has the static type Animal, but it refers to a Dog
    object. The overridden Dog.sound() implementation is therefore selected at runtime.
 

 

9. Multiple Child Classes with Polymorphism

 

class Animal {
  void sound() {
    print('Generic animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

class Cow extends Animal {
  @override
  void sound() {
    print('Cow moos');
  }
}

void main() {
  Animal animal1 = Dog();
  Animal animal2 = Cat();
  Animal animal3 = Cow();

  animal1.sound();
  animal2.sound();
  animal3.sound();
}

 

Output:

 

Dog barks
Cat meows
Cow moos

 


    This is a useful example of polymorphism because all three variables have the parent type
    Animal, while their actual objects are different child classes.
 

 

10. Polymorphism with a List

 


    One practical benefit of polymorphism is that a collection can contain objects of different child classes when
    they share a common parent type.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

class Cow extends Animal {
  @override
  void sound() {
    print('Cow moos');
  }
}

void main() {
  List<Animal> animals = [
    Dog(),
    Cat(),
    Cow(),
  ];

  for (Animal animal in animals) {
    animal.sound();
  }
}

 

Output:

 

Dog barks
Cat meows
Cow moos

 


    This design is especially useful when an application needs to process different types of objects using a common
    interface.
 

 

11. Using super with Method Overriding

 


    Sometimes the child class wants to keep the parent's behavior and then add additional behavior.
    In that case, the child can call the parent implementation using super.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    super.sound();
    print('Dog specifically barks');
  }
}

void main() {
  Dog dog = Dog();

  dog.sound();
}

 

Output:

 

Animal makes a sound
Dog specifically barks

 

12. Overriding a Method with Properties

 


    Dart also allows inherited getters and setters to be overridden when the overriding declaration is compatible with
    the inherited member.
 

 

class Person {
  String get role {
    return 'Person';
  }
}

class Student extends Person {
  @override
  String get role {
    return 'Student';
  }
}

void main() {
  Person person = Student();

  print(person.role);
}

 

Output:

 

Student

 

13. Practical Example: Employee Polymorphism

 


    Consider an application that manages different types of employees. Every employee can have a
    calculateSalary() operation, but the calculation can differ for each employee type.
 

 

class Employee {
  double calculateSalary() {
    return 0;
  }
}

class FullTimeEmployee extends Employee {
  double monthlySalary;

  FullTimeEmployee(this.monthlySalary);

  @override
  double calculateSalary() {
    return monthlySalary;
  }
}

class PartTimeEmployee extends Employee {
  double hourlyRate;
  int hours;

  PartTimeEmployee(this.hourlyRate, this.hours);

  @override
  double calculateSalary() {
    return hourlyRate * hours;
  }
}

void main() {
  Employee employee1 =
      FullTimeEmployee(60000);

  Employee employee2 =
      PartTimeEmployee(500, 80);

  print('Full-time salary: ${employee1.calculateSalary()}');
  print('Part-time salary: ${employee2.calculateSalary()}');
}

 

Output:

 

Full-time salary: 60000.0
Part-time salary: 40000.0

 


    The same calculateSalary() method call produces different results depending on the actual object.
 

 

14. Practical Example: Payment System

 


    Polymorphism is useful in payment systems where different payment methods need a common operation such as
    pay().
 

 

class Payment {
  void pay(double amount) {
    print('Processing payment of ₹$amount');
  }
}

class CreditCardPayment extends Payment {
  @override
  void pay(double amount) {
    print('Paid ₹$amount using Credit Card');
  }
}

class UpiPayment extends Payment {
  @override
  void pay(double amount) {
    print('Paid ₹$amount using UPI');
  }
}

class CashPayment extends Payment {
  @override
  void pay(double amount) {
    print('Paid ₹$amount using Cash');
  }
}

void main() {
  List<Payment> payments = [
    CreditCardPayment(),
    UpiPayment(),
    CashPayment(),
  ];

  for (Payment payment in payments) {
    payment.pay(1000);
  }
}

 

Output:

 

Paid ₹1000.0 using Credit Card
Paid ₹1000.0 using UPI
Paid ₹1000.0 using Cash

 

15. Practical Example: Notification System

 

class NotificationService {
  void send(String message) {
    print('Sending notification: $message');
  }
}

class EmailNotification extends NotificationService {
  @override
  void send(String message) {
    print('Email notification: $message');
  }
}

class SmsNotification extends NotificationService {
  @override
  void send(String message) {
    print('SMS notification: $message');
  }
}

class PushNotification extends NotificationService {
  @override
  void send(String message) {
    print('Push notification: $message');
  }
}

void main() {
  List<NotificationService> services = [
    EmailNotification(),
    SmsNotification(),
    PushNotification(),
  ];

  for (NotificationService service in services) {
    service.send('Your order has been shipped.');
  }
}

 


    The application can work with NotificationService without needing to hard-code separate processing
    logic for every notification type.
 

 

16. Practical Example: E-Commerce Products

 

class Product {
  String name;
  double price;

  Product(this.name, this.price);

  void displayDetails() {
    print('$name - ₹$price');
  }
}

class ElectronicsProduct extends Product {
  ElectronicsProduct(
    String name,
    double price,
  ) : super(name, price);

  @override
  void displayDetails() {
    print('Electronics: $name - ₹$price');
  }
}

class ClothingProduct extends Product {
  ClothingProduct(
    String name,
    double price,
  ) : super(name, price);

  @override
  void displayDetails() {
    print('Clothing: $name - ₹$price');
  }
}

void main() {
  List<Product> products = [
    ElectronicsProduct('Laptop', 65000),
    ClothingProduct('T-Shirt', 1200),
  ];

  for (Product product in products) {
    product.displayDetails();
  }
}

 

Output:

 

Electronics: Laptop - ₹65000.0
Clothing: T-Shirt - ₹1200.0

 

17. Practical Example: Shape Calculation

 


    A common polymorphism example is a shape hierarchy. Each shape can provide its own implementation of
    area().
 

 

class Shape {
  double area() {
    return 0;
  }
}

class Circle extends Shape {
  double radius;

  Circle(this.radius);

  @override
  double area() {
    return 3.14159 * radius * radius;
  }
}

class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double area() {
    return width * height;
  }
}

void main() {
  List<Shape> shapes = [
    Circle(5),
    Rectangle(10, 4),
  ];

  for (Shape shape in shapes) {
    print('Area: ${shape.area()}');
  }
}

 

18. Practical Example: Vehicles

 

class Vehicle {
  void start() {
    print('Vehicle is starting');
  }
}

class Car extends Vehicle {
  @override
  void start() {
    print('Car engine started');
  }
}

class Bike extends Vehicle {
  @override
  void start() {
    print('Bike engine started');
  }
}

class ElectricCar extends Vehicle {
  @override
  void start() {
    print('Electric motor started silently');
  }
}

void main() {
  List<Vehicle> vehicles = [
    Car(),
    Bike(),
    ElectricCar(),
  ];

  for (Vehicle vehicle in vehicles) {
    vehicle.start();
  }
}

 

19. Polymorphism in Flutter

 


    Polymorphism is useful in Flutter when different classes share a common abstraction but provide different
    implementations. Flutter's widget architecture also makes extensive use of class hierarchies and overridden
    methods.
 

 


    JustAcademy's Flutter curriculum includes Dart OOP concepts such as inheritance, polymorphism, and abstraction,
    followed by Flutter Widgets and UI Design. :contentReference[oaicite:1]{index=1}
 

 

StatelessWidget Example

 

import 'package:flutter/material.dart';

class WelcomeScreen extends StatelessWidget {
  const WelcomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Welcome to Flutter'),
      ),
    );
  }
}

 


    In this example, WelcomeScreen extends StatelessWidget and provides its own
    implementation of the inherited build() method.
 

 

20. Method Overriding in Flutter

 


    A common pattern in Flutter is extending a framework class and overriding a method to provide application-specific
    behavior.
 

 

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Text('Home Page'),
      ),
    );
  }
}

 


    The build() method is supplied by the inherited widget contract and is implemented specifically for
    HomePage.
 

 

21. Polymorphism with Abstract Classes

 


    Polymorphism is frequently used together with abstract classes. An abstract class can define a common contract
    that child classes implement differently.
 

 

abstract class Payment {
  void pay(double amount);
}

class UpiPayment extends Payment {
  @override
  void pay(double amount) {
    print('Paid ₹$amount using UPI');
  }
}

class CardPayment extends Payment {
  @override
  void pay(double amount) {
    print('Paid ₹$amount using Card');
  }
}

void main() {
  List<Payment> payments = [
    UpiPayment(),
    CardPayment(),
  ];

  for (Payment payment in payments) {
    payment.pay(500);
  }
}

 


    The abstract class establishes a common operation, while each concrete child class supplies its own
    implementation.
 

 

22. Method Overriding vs Method Overloading

 


    These two terms are often confused, but they describe different concepts.
 

 


   
     
       
       
       
     
   
   
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
   
 
FeatureMethod OverridingMethod Overloading
MeaningChild class provides a different implementation of an inherited member.Multiple methods/functions use the same name with different parameter lists in languages that support traditional overloading.
Inheritance Required?Yes, for inherited member overriding.Not necessarily.
Dart UsageSupported.Dart does not provide traditional method overloading based only on different parameter lists.
Example@override void sound()Use optional/named parameters or different method names instead.

 

23. Method Overriding vs Inheritance

 


   
     
       
       
     
   
   
     
       
       
     
     
       
       
     
     
       
       
     
   
 
ConceptPurpose
InheritanceAllows a child class to reuse and extend functionality from a parent class.
Method OverridingAllows a child class to replace inherited behavior with its own implementation.
PolymorphismAllows the same interface or operation to work with objects having different implementations.

 

24. Static Type vs Runtime Object

 


    One important idea behind polymorphism is the distinction between the variable's declared type and the object's
    actual runtime type.
 

 

Animal animal = Dog();

 

Here:

 


       
  • Animal is the declared/static type of the variable.

  •    
  • Dog() creates the actual object.

  •    
  • The object is a Dog.

  •    
  • Overridden behavior is selected according to the actual object.

  •  

 

25. A Practical Polymorphic Function

 


    Polymorphism becomes especially useful when a function accepts a parent type rather than a specific child type.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

void makeSound(Animal animal) {
  animal.sound();
}

void main() {
  makeSound(Dog());
  makeSound(Cat());
}

 

Output:

 

Dog barks
Cat meows

 


    The makeSound() function does not need separate versions for Dog and Cat.
    It works with the common Animal type.
 

 

26. Practical Example: Delivery System

 

abstract class Delivery {
  void deliver(String orderId);
}

class BikeDelivery extends Delivery {
  @override
  void deliver(String orderId) {
    print('Order $orderId delivered by bike');
  }
}

class TruckDelivery extends Delivery {
  @override
  void deliver(String orderId) {
    print('Order $orderId delivered by truck');
  }
}

class DroneDelivery extends Delivery {
  @override
  void deliver(String orderId) {
    print('Order $orderId delivered by drone');
  }
}

void processDelivery(
  Delivery delivery,
  String orderId,
) {
  delivery.deliver(orderId);
}

void main() {
  processDelivery(BikeDelivery(), 'ORD101');
  processDelivery(TruckDelivery(), 'ORD102');
  processDelivery(DroneDelivery(), 'ORD103');
}

 


    This design makes it easy to add another delivery type without changing the function that processes deliveries.
 

 

27. Advantages of Polymorphism

 


       
  • Flexibility: The same interface can work with different object types.

  •    
  • Reusability: Common processing logic can be reused.

  •    
  • Maintainability: Specialized behavior stays inside the relevant class.

  •    
  • Extensibility: New child classes can be introduced with minimal changes to existing code.

  •    
  • Loose Coupling: Code can depend on a common abstraction rather than concrete implementations.

  •    
  • Cleaner Architecture: Different behaviors can be separated into specialized classes.

  •  

 

28. Advantages of Method Overriding

 


       
  • Allows child classes to customize parent behavior.

  •    
  • Supports runtime polymorphism.

  •    
  • Encourages specialized implementations.

  •    
  • Reduces the need for large conditional statements.

  •    
  • Makes class hierarchies more flexible.

  •    
  • Works naturally with abstract classes and interfaces.

  •  

 

29. Common Mistakes

 

Mistake 1: Forgetting @override

 

Prefer:

 

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

 

Mistake 2: Changing the Contract Incorrectly

 


    An overriding method needs to remain compatible with the inherited member's contract. Avoid changing the method
    in a way that violates the expectations of code using the parent type.
 

 

Mistake 3: Confusing Overriding with Overloading

 


    Overriding involves an inherited member and a subclass implementation. Traditional method overloading based only
    on different parameter lists is not a Dart feature.
 

 

Mistake 4: Calling Child-Specific Members Through a Parent Type

 

Animal animal = Dog();

// animal.bark(); // Not available through Animal type

 


    The variable's static type determines which members are directly accessible through that variable.
 

 

30. Best Practices

 


       
  • Use @override for overridden members.

  •    
  • Design parent classes around meaningful common behavior.

  •    
  • Use polymorphism when different classes need to satisfy a common contract.

  •    
  • Prefer abstractions when application code should not depend on specific implementations.

  •    
  • Keep each overridden method focused on the responsibility of its child class.

  •    
  • Avoid deep and unnecessarily complicated inheritance hierarchies.

  •    
  • Use super when the parent implementation should also participate in the behavior.

  •    
  • Use composition when inheritance does not represent a genuine "is-a" relationship.

  •  

 

31. Quick Revision Program

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

void makeSound(Animal animal) {
  animal.sound();
}

void main() {
  List<Animal> animals = [
    Dog(),
    Cat(),
  ];

  for (Animal animal in animals) {
    makeSound(animal);
  }
}

 

Output:

 

Dog barks
Cat meows

 

32. Polymorphism and Method Overriding – Quick Comparison

 


   
     
       
       
       
     
   
   
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
   
 
TopicMeaningExample
InheritanceChild class receives and extends parent functionality.class Dog extends Animal
Method OverridingChild class provides its own implementation of an inherited method.@override void sound()
PolymorphismSame interface or operation can produce different behavior for different objects.Animal animal = Dog()
superAccesses parent functionality.super.sound()
@overrideMarks an intentional override.@override

 

33. Practice Exercises

 


       
  1. Create an Animal class with a sound() method.

  2.    
  3. Create Dog, Cat, and Cow classes that override sound().

  4.    
  5. Create a parent Vehicle class and override start() in Car and Bike.

  6.    
  7. Create a Payment class and implement UPIPayment, CardPayment, and CashPayment.

  8.    
  9. Create a list of parent-type objects and call the overridden method for every object.

  10.    
  11. Create a function that accepts a parent type and demonstrate polymorphism.

  12.    
  13. Use super inside an overridden method.

  14.    
  15. Create an abstract Shape class and implement Circle and Rectangle.

  16.    
  17. Create a Flutter widget by extending StatelessWidget and identify the overridden build() method.

  18.    
  19. Build a small e-commerce example where different product classes override a common displayDetails() method.

  20.  

 

34. Key Takeaways

 


       
  • Polymorphism means that one common interface can represent different behaviors.

  •    
  • Method overriding allows a child class to provide its own implementation of an inherited method.

  •    
  • The @override annotation identifies an intentional override.

  •    
  • The super keyword can be used to call parent functionality.

  •    
  • A parent-type variable can refer to an object of a child class.

  •    
  • Runtime polymorphism allows the appropriate overridden implementation to execute for the actual object.

  •    
  • Lists can contain objects of different child classes through a common parent type.

  •    
  • Polymorphism helps reduce conditional logic and improve extensibility.

  •    
  • Abstract classes can provide common contracts for polymorphic designs.

  •    
  • Flutter uses class hierarchies and overridden methods extensively, so these Dart concepts are important for Flutter development.

  •  

 

35. Learn Flutter with JustAcademy

 


    JustAcademy's Flutter training curriculum includes Dart Programming Fundamentals with Object-Oriented Programming,
    classes, objects, constructors, inheritance, polymorphism, and abstraction. The curriculum then progresses to
    Flutter widgets and UI design and further application-development topics. :contentReference[oaicite:2]{index=2}
 

 


   
      View JustAcademy Flutter Training
   

 

 


   
      Register for JustAcademy Course Demo
   

 

whatsapp