Menu
Coddy logo textTech

Полиморфизм через интерфейсы

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 58 из 110.

В предыдущем уроке вы познакомились с полиморфизмом через наследование — использованием типа родительского класса для работы с дочерними объектами. Но полиморфизм также работает через интерфейсы, и этот подход часто оказывается более гибким.

Когда классы реализуют один и тот же интерфейс, вы можете работать с ними полиморфно, даже если они не связаны отношением наследования:

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 и Receipt — это совершенно не связанные классы, у них нет общего родителя. Тем не менее, поскольку все они реализуют Printable, мы можем хранить их в одном списке и вызывать printDetails() для каждого из них.

Это мощный инструмент, так как он позволяет определять контракты поведения, не навязывая иерархию наследования. Любой класс может реализовать интерфейс, независимо от того, что он расширяет. Ваш код становится более модульным, так как вы можете добавлять новые типы, поддерживающие печать, не изменяя существующий код — достаточно просто реализовать интерфейс.

challenge icon

Задание

Легко

Давайте создадим систему медиаплеера, которая демонстрирует полиморфизм через интерфейсы. Вы создадите интерфейс, определяющий, что значит быть «воспроизводимым» (playable), а затем реализуете несколько несвязанных типов медиа, которые выполняют этот контракт — это позволит обрабатывать их единообразно, несмотря на отсутствие отношений наследования.

Вы организуете свой код в два файла:

  • media.dart: Определите здесь свой интерфейс и классы медиа:
    • Абстрактный класс Playable с двумя абстрактными методами: play() и String getMediaType()
    • Класс Song, который реализует Playable. Он должен иметь String title и String artist. Его метод play() выводит Playing song: [title] by [artist], а getMediaType() возвращает 'Audio'
    • Класс Video, который реализует Playable. Он должен иметь String title и int duration (в минутах). Его метод play() выводит Playing video: [title] ([duration] min), а getMediaType() возвращает 'Video'
    • Класс Podcast, который реализует Playable. Он должен иметь String title и int episode. Его метод play() выводит Playing podcast: [title] - Episode [episode], а getMediaType() возвращает 'Podcast'
    Также создайте функцию playAll(List<Playable> playlist), которая перебирает список и для каждого элемента выводит [Media Type]:, после чего вызывает play() для этого элемента.
  • main.dart: Импортируйте файл медиа и продемонстрируйте полиморфизм на основе интерфейсов:
    • Создайте List<Playable>, содержащий Song с названием 'Imagine' и исполнителем 'John Lennon', Video с названием 'Dart Tutorial' и длительностью 15, и Podcast с названием 'Tech Talk' и эпизодом 42
    • Передайте этот список в функцию playAll()

Обратите внимание, что Song, Video и Podcast не имеют общего родительского класса — это совершенно не связанные типы. Тем не менее, поскольку все они реализуют Playable, вы можете хранить их в одном списке и работать с ними единообразно через интерфейс.

Ожидаемый вывод:

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

Шпаргалка

Полиморфизм работает через интерфейсы, позволяя единообразно обрабатывать несвязанные классы без отношений наследования.

Классы, реализующие один и тот же интерфейс, могут использоваться полиморфно:

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);
}

Полиморфизм на основе интерфейсов позволяет вам:

  • Определять контракты поведения без принудительного создания иерархий наследования
  • Хранить несвязанные типы в одной коллекции
  • Добавлять новые типы без изменения существующего кода
  • Делать код более модульным и гибким

Попробуйте сами

import 'media.dart';

void main() {
  // TODO: Создайте List<Playable>, содержащий:
  // - Песню (Song) с названием 'Imagine' и исполнителем 'John Lennon'
  // - Видео (Video) с названием 'Dart Tutorial' и длительностью 15
  // - Подкаст (Podcast) с названием 'Tech Talk' и эпизодом 42
  
  // TODO: Передайте список в playAll()
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование