Menu
Coddy logo textTech

Polymorphism via Interfaces

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 58 of 110.

In the previous lesson, you saw polymorphism through inheritance - using a parent class type to work with child objects. But polymorphism also works through interfaces, and this approach is often more flexible.

When classes implement the same interface, you can treat them polymorphically even if they have no inheritance relationship:

abstract class Printable {
  void printDetails();
}

class Invoice implements Printable {
  @override
  void printDetails() => print('Invoice #1234');
}

class Report implements Printable {
  @override
  void printDetails() => print('Monthly Report');
}

class Receipt implements Printable {
  @override
  void printDetails() => print('Receipt for purchase');
}

void printAll(List<Printable> items) {
  for (var item in items) {
    item.printDetails();
  }
}

void main() {
  var documents = [Invoice(), Report(), Receipt()];
  printAll(documents);
}

Invoice, Report, and Receipt are completely unrelated classes - they don't share a parent. Yet because they all implement Printable, we can store them in the same list and call printDetails() on each one.

This is powerful because it lets you define behavior contracts without forcing an inheritance hierarchy. Any class can implement an interface, regardless of what it extends. Your code becomes more modular since you can add new printable types without modifying existing code - just implement the interface.

challenge icon

Challenge

Easy

Let's build a media player system that demonstrates polymorphism through interfaces. You'll create an interface that defines what it means to be "playable," then implement several unrelated media types that all fulfill this contract - allowing them to be handled uniformly despite having no inheritance relationship.

You'll organize your code into two files:

  • media.dart: Define your interface and media classes here:
    • An abstract class Playable with two abstract methods: play() and String getMediaType()
    • A Song class that implements Playable. It should have a String title and String artist. Its play() method prints Playing song: [title] by [artist] and getMediaType() returns 'Audio'
    • A Video class that implements Playable. It should have a String title and int duration (in minutes). Its play() method prints Playing video: [title] ([duration] min) and getMediaType() returns 'Video'
    • A Podcast class that implements Playable. It should have a String title and int episode. Its play() method prints Playing podcast: [title] - Episode [episode] and getMediaType() returns 'Podcast'
    Also create a function playAll(List<Playable> playlist) that iterates through the list and for each item prints [Media Type]: followed by calling play() on that item.
  • main.dart: Import your media file and demonstrate interface-based polymorphism:
    • Create a List<Playable> containing a Song with title 'Imagine' and artist 'John Lennon', a Video with title 'Dart Tutorial' and duration 15, and a Podcast with title 'Tech Talk' and episode 42
    • Pass this list to playAll()

Notice how Song, Video, and Podcast share no parent class - they're completely unrelated types. Yet because they all implement Playable, you can store them in the same list and treat them uniformly through the interface.

Expected output:

Audio:
Playing song: Imagine by John Lennon
Video:
Playing video: Dart Tutorial (15 min)
Podcast:
Playing podcast: Tech Talk - Episode 42

Cheat sheet

Polymorphism works through interfaces, allowing unrelated classes to be treated uniformly without inheritance relationships.

Classes implementing the same interface can be used polymorphically:

abstract class Printable {
  void printDetails();
}

class Invoice implements Printable {
  @override
  void printDetails() => print('Invoice #1234');
}

class Report implements Printable {
  @override
  void printDetails() => print('Monthly Report');
}

void printAll(List<Printable> items) {
  for (var item in items) {
    item.printDetails();
  }
}

void main() {
  var documents = [Invoice(), Report()];
  printAll(documents);
}

Interface-based polymorphism allows you to:

  • Define behavior contracts without forcing inheritance hierarchies
  • Store unrelated types in the same collection
  • Add new types without modifying existing code
  • Make code more modular and flexible

Try it yourself

import 'media.dart';

void main() {
  // TODO: Create a List<Playable> containing:
  // - A Song with title 'Imagine' and artist 'John Lennon'
  // - A Video with title 'Dart Tutorial' and duration 15
  // - A Podcast with title 'Tech Talk' and episode 42
  
  // TODO: Pass the list to playAll()
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming