О чем статья
Ключевое слово covariant было внедрено в Dart для борьбы с важной проблемой переопределения методов.
В статье содержатся разбор проблемы, описание работы covariant и пример его использования для решения проблемы.
Проблема
Если в дочернем классе переопределяется метод родительского класса, то параметр метода дочернего класса может быть экземпляром только того класса, что указан в данного параметра в методе родительского класса.
Т.о., если в переопределяемом методе использовать параметр класса, который является дочерним для класса, который указан для параметра в методе наследуемого класса, то возникнет ошибка. Пример представлен на Рис. 1.
Cat - это класс-наследник Animal, который переопределяет метод eat. Animal в методе eat требует параметр типа данных Food.
Fish - это подкласс Food - т.е. неявно экземпляры класса Fish являются экземплярами класса Food. Однако, если в Cat.eat указать параметр с типом данных Fish, то возникнет ошибка.
Аналогичная ситуация с реализацией интерфейса (см. Рис.2).
Чтобы решить эту проблему, в Dart 2.12 было введено ключевое слово covariant.
Что такое covariant
covariant - это ключевое слово, которое используется вместе с параметрами методов при переопределении в дочерних классах методов родительских классов для того, чтобы решить проблему нераспознавания дочерних классов при переопределении методов.
Параметр дочернего метода вместо типа данных, указанного в переопределяемом методе родительского класса, может использовать тип данных, который является дочерним для указанного в методе родительского класса. Пример представлен на Рис.3.
Ключевое слово covariant сообщает анализатору то, что не нужно осуществлять строгую проверку типа данных. Однако, если использовать для параметра тип данных, который не является дочерним классом класса, указанного для параметра в методе родительского класса, то возникнет ошибка (см. Рис. 4).
Ключевое слово covariant может быть использовано в методе суперкласса или в методе производного класса. Однако, хорошей практикой считается использование covariant в методах родительского класса, чтобы в интерфейсе родительского класса явно указать то, что дочерние классы могут изменять этот интерфейс. Также такой подход позволяет всем дочерним классам в переопределении метода изменять тип данных для параметра, определенного с covariant. Пример представлен на Рис. 5.
Заключение
Ключевое слово covariant знать полезно, ведь что-то аналогичное примеру, представленному в статье, очень даже может встретиться в реальной жизни. Также covariant часто используется в исходниках Flutter и его не редко спрашивают на собеседованиях.
Благодарю за внимание!
Код из примеров
class Food {}
class Animal {
void eat(covariant Food food) {
print('Animal eats $food');
}
}
class Fish extends Food {}
class Cat extends Animal {
@override
void eat(Fish food) {
print('Cat eats $food');
}
}
class Meat extends Food {}
class Dog extends Animal {
@override
void eat(Meat food) {
print('Dog eats $food');
}
}
void main() {
Food food = Food();
Animal animal = Animal();
animal.eat(food);
Fish fish = Fish();
Cat cat = Cat();
cat.eat(fish);
Meat meat = Meat();
Dog dog = Dog();
dog.eat(meat);
}
Комментарии (10)
ParaMara
07.10.2024 04:42Приятно видеть, что авторы Хабра могут получить статью просто ткнув пальцем в любую букву любой документации. Осталось научиться получать минимально содержательную статью для чего в данном случае следовало написать
почему нельзя просто считать что всё covariant
как с этим в других языках
и для фанатов - когда в Dart было введено covariant
maratxat Автор
07.10.2024 04:42приятно видеть, комментарий от внимательного и справедливого читателя
и для фанатов - когда в Dart было введено covariant
в статье это указано
авторы Хабра могут получить статью просто ткнув пальцем в любую букву любой документации.
тогда не поленитесь и тыкните в эту документацию, чтобы сравнить мою статью с ней. возможно, какие-то отличия обнаружите
как с этим в других языках
я до этого имел опыт работы только с динамически типизированными япами - там таких проблем априори не может быть. поэтому, как там у других это реализовано вам поможет гугл, а эта статья про дарт
почему нельзя просто считать что всё covariant
не понял вопроса, сформулируйте свои мысли получше.
Dragon274
07.10.2024 04:42+2Почему используя для параметра тип данных, который не дочерний классом класса, указанного для параметра в методе род. класса возникает ошибка??
maratxat Автор
07.10.2024 04:42так устроен статический анализатор дарта. а почему он так устроен - можно только догадываться.
мне кажется, что разработчики дарта так сделали, чтобы отношения обобщения имели высокую строгость для дочерних классов при их наследовании и реализации интерфейсов
HemulGM
Таким образом получается, что класс Cat не может принимать параметром, например, `Meat food`?
Но таким образом получается, что если Cat как Animal в общем списке Animals, то мы можем передать в него любой Food, и объект Cat примет Meat и будет обращаться к Meat как к Fish. Это как-то регулируется вообще?
maratxat Автор
да, верно - так происходит из-за того, в Cat.eat мы указали параметр food с типом данным Fish, а Meat - это не Fish и даже не подкласс Fish. статический анализатор не пропустить аргумент типа Meat в Cat.eat
нет, не верно. кастинг инстанса дочернего класса Cat к родительскому Animal не меняет поведения дочернего класса - метод Cat.eat по прежнему будет требовать от инжектора инстанс класса Fish и т.о. Meat не подойдет
HemulGM
Создайте список из объектов класса Animal, добавьте туда Cat, Dog и вызывайте Eat для каждого, передавая туда Meat
maratxat Автор
Создал, вызвал, получил ошибку, о которой писал в ответе на ваш комментарий
HemulGM
Так об этом я и говорил. Ошибка проявляется в рантайме, а не при написании кода.
maratxat Автор
про то, где произойдет ошибка вы вообще ничего не говорили, поэтому я это воспринял как сомнение в том, будет ли вообще возникать ошибка
ошибка в листе - это проблема тайп кастинга элементов листа а не наследования
если в Cat.eat передать meat, то анализатор задетектит эту ошибку до run-time