Polymorphism in Dart – Dart Object Oriented Programming (OOP) Guide

Haris Bin Nasir Avatar

·

·

Polymorphism is one of the core principles of Object-Oriented Programming (OOP). It allows objects to be treated as instances of their parent class, enabling flexibility and extensibility in code. In Dart, polymorphism plays a significant role in simplifying complex code and promoting reusability. This guide will walk you through the concept of polymorphism in Dart, how it works, and its practical applications with detailed examples.

For further information on other topics of Dart, click here.

For an in-depth exploration of other OOP topics in Dart, such as inheritance, polymorphism, and encapsulation etc, click here.

What is Polymorphism?

Polymorphism refers to the ability of different classes to respond to the same method call in their own way. In Dart, this is achieved through method overriding and interface implementation, where a subclass can provide its specific implementation for a method defined in the superclass. Polymorphism ensures that the same method behaves differently for different objects, enhancing flexibility and reusability.

Key Concepts of Polymorphism in Dart:

  1. Method Overriding: A subclass provides a specific implementation of a method that is already defined in the superclass.
  2. Interfaces: Classes can implement interfaces (abstract classes or normal classes) to define their specific behaviors.
  3. Dynamic Dispatch: The correct method is called based on the actual object at runtime, not at compile time.

Method Overriding

Method Overriding is a form of polymorphism in which a subclass provides a specific implementation of a method already defined in its superclass. The @override annotation is used in Dart to indicate that a method is being overridden.

Example of Method Overriding
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");
  }
}

void main() {
  Animal myDog = Dog();
  Animal myCat = Cat();

  myDog.sound();  // Output: Dog barks
  myCat.sound();  // Output: Cat meows
}

Explanation:

In this example, both the Dog and Cat classes inherit from the Animal class but override the sound() method to provide their specific implementations. This demonstrates polymorphism because the same method sound() behaves differently for different objects.


Polymorphism with Interfaces

Polymorphism in Dart can also be achieved through interfaces. In Dart, every class can act as an interface, meaning that other classes can implement that class to inherit its properties and methods, or define their specific behavior for the methods.

Example of Polymorphism with Interfaces
abstract class Shape {
  void draw();  // Abstract method
}

class Circle implements Shape {
  @override
  void draw() {
    print("Drawing a circle");
  }
}

class Rectangle implements Shape {
  @override
  void draw() {
    print("Drawing a rectangle");
  }
}

void main() {
  Shape shape1 = Circle();
  Shape shape2 = Rectangle();

  shape1.draw();  // Output: Drawing a circle
  shape2.draw();  // Output: Drawing a rectangle
}

Explanation:

Here, both Circle and Rectangle implement the Shape interface and provide their own implementation for the draw() method. This demonstrates polymorphism because the same interface method draw() behaves differently based on the object that implements it.


Dynamic Dispatch

In polymorphism, dynamic dispatch refers to the process by which a method call is resolved at runtime, not at compile time. This ensures that the correct method implementation is called based on the actual object, even if the method is invoked using a reference of the superclass type.

Example of Dynamic Dispatch
class Employee {
  void work() {
    print("Employee works");
  }
}

class Manager extends Employee {
  @override
  void work() {
    print("Manager oversees work");
  }
}

class Developer extends Employee {
  @override
  void work() {
    print("Developer writes code");
  }
}

void main() {
  Employee emp = Manager();
  emp.work();  // Output: Manager oversees work

  emp = Developer();
  emp.work();  // Output: Developer writes code
}

Explanation:

In this example, the Employee reference emp is used to invoke the work() method, but the actual method called is determined at runtime based on the object (Manager or Developer) assigned to the reference. This is the essence of dynamic dispatch in polymorphism.


Polymorphism in Collections

Polymorphism is often used in collections, where a list of objects can contain different types of objects that share a common superclass or interface. Each object in the collection can respond to the same method call in its unique way.

Example of Polymorphism in Collections
class Animal {
  void sound() {
    print("Some animal sound");
  }
}

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

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

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

  for (var animal in animals) {
    animal.sound();  // Output: Dog barks, Cat meows
  }
}

Explanation:

In this example, a list of Animal objects contains instances of both Dog and Cat. When iterating through the list, each object responds to the sound() method according to its specific implementation, demonstrating polymorphism in collections.


Benefits of Polymorphism

Polymorphism provides several key benefits in Dart, especially in the context of object-oriented programming:

  1. Code Reusability: Polymorphism allows you to write more reusable code. For example, a method can operate on objects of a superclass or interface type, without needing to know the specifics of each subclass or implementation.
  2. Flexibility: Polymorphism increases the flexibility of the code by allowing the same interface or superclass to be used for different object types.
  3. Extensibility: It makes the system more extendable since new classes can be added without modifying existing code, as long as they implement the necessary methods.

Best Practices for Using Polymorphism in Dart

  1. Use Polymorphism to Enhance Code Flexibility: Use polymorphism when you want the ability to treat objects of different classes in a consistent manner.
  2. Leverage Interfaces: Implement interfaces in Dart to create more reusable and modular code.
  3. Avoid Overcomplicating Code: While polymorphism is a powerful tool, overusing it can make the code harder to understand and maintain. Use it when it simplifies the design.

Conclusion

Polymorphism is a vital feature of Object-Oriented Programming in Dart, allowing you to design flexible and reusable systems. By using method overriding, interfaces, and dynamic dispatch, you can enable objects to respond differently to the same method call based on their actual type. Understanding and applying polymorphism effectively will make your Dart applications more scalable and maintainable. For further details and more complex use cases of polymorphism in Dart, be sure to check out our detailed articles.

Happy Coding…!!!

Leave a Reply

Your email address will not be published. Required fields are marked *