Полиморфизм через интерфейсы
Часть раздела Объектно-ориентированное программирование путешествия по 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() для каждого из них.
Это мощный инструмент, так как он позволяет определять контракты поведения, не навязывая иерархию наследования. Любой класс может реализовать интерфейс, независимо от того, что он расширяет. Ваш код становится более модульным, так как вы можете добавлять новые типы, поддерживающие печать, не изменяя существующий код — достаточно просто реализовать интерфейс.
Задание
ЛегкоДавайте создадим систему медиаплеера, которая демонстрирует полиморфизм через интерфейсы. Вы создадите интерфейс, определяющий, что значит быть «воспроизводимым» (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()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий