Основы полиморфизма
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 57 из 110.
Полиморфизм означает «много форм» — это способность работать с объектами разных классов через общий интерфейс. Когда у вас есть ссылка на родительский класс, она может указывать на любой объект дочернего класса, и вызывается соответствующий метод в зависимости от фактического типа объекта.
Это мощный инструмент, так как вы можете писать код, который работает с родительским типом, но при этом автоматически корректно обрабатывает все дочерние типы:
class Animal {
void speak() => print('Some sound');
}
class Dog extends Animal {
@override
void speak() => print('Woof!');
}
class Cat extends Animal {
@override
void speak() => print('Meow!');
}
void main() {
Animal myPet = Dog(); // Родительский тип, дочерний объект
myPet.speak(); // Вывод: Woof!
myPet = Cat(); // Та же переменная, другой объект
myPet.speak(); // Вывод: Meow!
}Переменная myPet объявлена как Animal, но она содержит Dog или Cat. Когда вызывается speak(), Dart проверяет фактический тип объекта во время выполнения и вызывает соответствующий переопределенный метод.
Это позволяет вам писать гибкие функции, которые работают с любым подклассом:
void makeAllSpeak(List<Animal> animals) {
for (var animal in animals) {
animal.speak(); // Каждое животное говорит по-своему
}
}
void main() {
var pets = [Dog(), Cat(), Dog()];
makeAllSpeak(pets);
// Вывод: Woof! Meow! Woof!
}Функции не нужно знать о Dog или Cat — она просто работает с Animal. Это делает ваш код более расширяемым, так как добавление новых типов животных не требует изменений в существующих функциях.
Задание
ЛегкоДавайте создадим систему музыкальных инструментов, которая демонстрирует полиморфизм в действии. Вы создадите базовый класс Instrument и несколько типов инструментов, а затем напишете функцию, которая заставит любую коллекцию инструментов играть вместе — не зная их конкретных типов.
Вы организуете свой код в два файла:
instruments.dart: Определите здесь иерархию ваших инструментов:- Класс
Instrumentсо свойствомString nameи конструктором. Включите методplay(), который выводит[name] makes a sound - Класс
Guitar, который расширяетInstrumentи переопределяетplay()для вывода[name] strums melodically - Класс
Drum, который расширяетInstrumentи переопределяетplay()для вывода[name] beats rhythmically - Класс
Piano, который расширяетInstrumentи переопределяетplay()для вывода[name] plays harmoniously
performConcert(List<Instrument> instruments), которая выводитConcert begins!, затем перебирает список, вызываяplay()для каждого инструмента, и в конце выводитConcert ends!- Класс
main.dart: Импортируйте файл с инструментами и продемонстрируйте полиморфизм, создав смешанную группу:- Создайте список типа
List<Instrument>, содержащийGuitarс именем'Acoustic',Drumс именем'Snare'иPianoс именем'Grand' - Передайте этот список в функцию
performConcert()
- Создайте список типа
Обратите внимание, как performConcert() работает с родительским типом Instrument, но при этом каждый инструмент играет по-своему. Это и есть полиморфизм — один и тот же вызов метода приводит к разному поведению в зависимости от фактического типа объекта.
Ожидаемый вывод:
Concert begins!
Acoustic strums melodically
Snare beats rhythmically
Grand plays harmoniously
Concert ends!Шпаргалка
Полиморфизм означает «много форм» — он позволяет работать с объектами разных классов через общий интерфейс. Ссылка на родительский класс может указывать на любой объект дочернего класса, и при выполнении программы вызывается правильный метод в зависимости от фактического типа объекта.
Базовый пример полиморфизма:
class Animal {
void speak() => print('Some sound');
}
class Dog extends Animal {
@override
void speak() => print('Woof!');
}
class Cat extends Animal {
@override
void speak() => print('Meow!');
}
void main() {
Animal myPet = Dog(); // Родительский тип, дочерний объект
myPet.speak(); // Output: Woof!
myPet = Cat(); // Та же переменная, другой объект
myPet.speak(); // Output: Meow!
}Полиморфизм позволяет создавать гибкие функции, которые работают с любым подклассом:
void makeAllSpeak(List<Animal> animals) {
for (var animal in animals) {
animal.speak(); // Каждое животное «говорит» по-своему
}
}
void main() {
var pets = [Dog(), Cat(), Dog()];
makeAllSpeak(pets);
// Output: Woof! Meow! Woof!
}Функция работает с родительским типом Animal, и ей не нужно знать о конкретных дочерних классах, таких как Dog или Cat. Это делает код более расширяемым — добавление новых типов не требует изменений в существующих функциях.
Попробуйте сами
import 'instruments.dart';
void main() {
// TODO: Создайте List<Instrument>, содержащий:
// — Guitar с именем 'Acoustic'
// — Drum с именем 'Snare'
// — Piano с именем 'Grand'
// TODO: Вызовите performConcert() со своим списком инструментов
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий