Ключевое слово covariant
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 61 из 110.
Когда вы переопределяете метод в подклассе, Dart обычно требует, чтобы типы параметров совпадали точно. Но иногда вам нужно, чтобы метод подкласса принимал более специфичный тип, чем родительский. Ключевое слово covariant позволяет это сделать.
Рассмотрим сценарий, в котором класс Animal имеет метод, который принимает другое Animal:
class Animal {
void chase(Animal other) {
print('Chasing an animal');
}
}
class Dog extends Animal {
@override
void chase(covariant Dog other) {
print('Dog chasing another dog');
}
}Без covariant Dart выдал бы ошибку, что Dog не является тем же типом, что и Animal. Добавив covariant перед параметром, вы сообщаете Dart: «Я знаю, что это более специфичный тип, и я гарантирую, что будет передан только правильный тип».
Это полезно, когда подклассы по логике должны работать только с объектами своего типа:
void main() {
Dog dog1 = Dog();
Dog dog2 = Dog();
dog1.chase(dog2); // Работает: Собака преследует другую собаку
}Будьте осторожны — использование covariant переносит проверку безопасности типов на этап выполнения. Если вы вызовете метод через ссылку родительского класса с неверным типом, вы получите ошибку времени выполнения вместо ошибки компиляции. Используйте это, когда вы уверены в передаваемых типах.
Задание
ЛегкоДавайте создадим систему совместимости домашних животных, которая использует ключевое слово covariant, чтобы гарантировать, что животные могут играть только с представителями своего вида. Вы создадите иерархию домашних животных, где каждый тип имеет метод playWith, принимающий только тот же тип животного.
Вы организуете свой код в два файла:
pets.dart: Определите здесь вашу иерархию животных:- Класс
Petсо свойствомString nameи конструктором. Включите методplayWith(Pet other), который выводит[name] plays with [other.name] - Класс
Cat, который расширяетPetи переопределяетplayWith, используя ключевое словоcovariant, чтобы принимать толькоCat. Он должен выводить[name] and [other.name] chase yarn together - Класс
Dog, который расширяетPetи переопределяетplayWith, используя ключевое словоcovariant, чтобы принимать толькоDog. Он должен выводить[name] and [other.name] fetch the ball together - Класс
Rabbit, который расширяетPetи переопределяетplayWith, используя ключевое словоcovariant, чтобы принимать толькоRabbit. Он должен выводить[name] and [other.name] hop around together
- Класс
main.dart: Импортируйте файл с животными и продемонстрируйте, как каждый тип животных играет исключительно со своим видом:- Создайте два объекта
Catс именами'Whiskers'и'Mittens', затем заставьте Whiskers играть с Mittens - Создайте два объекта
Dogс именами'Buddy'и'Max', затем заставьте Buddy играть с Max - Создайте два объекта
Rabbitс именами'Flopsy'и'Cotton', затем заставьте Flopsy играть с Cotton
- Создайте два объекта
Ключевое слово covariant позволяет каждому подклассу сузить тип параметра с Pet до его конкретного типа, обеспечивая типобезопасное взаимодействие между животными одного вида.
Ожидаемый вывод:
Whiskers and Mittens chase yarn together
Buddy and Max fetch the ball together
Flopsy and Cotton hop around togetherШпаргалка
Ключевое слово covariant позволяет подклассу переопределять метод с более специфичным типом параметра, чем у родительского класса.
Без covariant Dart требует, чтобы типы параметров точно совпадали при переопределении методов. С covariant вы можете сузить тип параметра в подклассе:
class Animal {
void chase(Animal other) {
print('Chasing an animal');
}
}
class Dog extends Animal {
@override
void chase(covariant Dog other) {
print('Dog chasing another dog');
}
}Это полезно, когда подклассы должны работать только со своим собственным типом:
void main() {
Dog dog1 = Dog();
Dog dog2 = Dog();
dog1.chase(dog2); // Works: Dog chasing another dog
}Важно: Использование covariant переносит проверку типов со времени компиляции на время выполнения. Если вы вызовете метод через ссылку родительского класса с неправильным типом, вы получите ошибку времени выполнения вместо ошибки компиляции.
Попробуйте сами
import 'pets.dart';
void main() {
// TODO: Создайте два объекта Cat с именами 'Whiskers' и 'Mittens'
// Затем заставьте Whiskers поиграть с Mittens
// TODO: Создайте два объекта Dog с именами 'Buddy' и 'Max'
// Затем заставьте Buddy поиграть с Max
// TODO: Создайте два объекта Rabbit с именами 'Flopsy' и 'Cotton'
// Затем заставьте Flopsy поиграть с Cotton
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий