Основы объектно-ориентированного программирования
Шрифт:
Результат последнего вызова, вполне возможно приятный для юношей, - это именно то, что мы пытались не допустить с помощью переопределения типов. Вызов share ведет к тому, что объект BOY, известный как b и благодаря полиморфизму получивший псевдоним s типа SKIER,
Схема с параллельной иерархией столь же проста: заменим SKIER на SKIER1, вызов share– на вызов s.accommodate (gr), где gr– сущность типа GIRL_ROOM. Результат - тот же.
При контравариантном решении этих проблем не возникало бы: специализация цели вызова (в нашем примере s) требовала бы обобщения аргумента. Контравариантность в результате ведет к более простой математической модели механизма: наследование - переопределение - полиморфизм. Данный факт описан в ряде теоретических статей, предлагающих эту стратегию. Аргументация не слишком убедительна, поскольку, как показывают наши примеры и другие публикации, контравариантность не имеет практического использования.
В литературе для программистов нередко встречается призыв к методам, основанных на простых математических моделях. Однако математическая красота - всего лишь один из критериев ценности результата, - есть и другие - полезность и реалистичность. |
Поэтому, не пытаясь натянуть контравариантную одежду на ковариантное тело, следует принять ковариантную действительность и искать пути устранения нежелательного эффекта.
Скрытие потомком
Прежде чем искать решение проблемы ковариантности, рассмотрим еще один механизм, способный в условиях полиморфизма привести к нарушениям типа. Скрытие потомком (descendant hiding) - это способность класса не экспортировать компонент, полученный от родителей.
Рис. 17.8. Скрытие потомком
Типичным примером является компонент add_vertex (добавить вершину), экспортируемый классом POLYGON, но скрываемый его потомком RECTANGLE (ввиду возможного нарушения инварианта - класс хочет оставаться прямоугольником):
Не программистский пример: класс "Страус" скрывает метод "Летать", полученный от родителя "Птица".
Давайте
Так как объект r скрывается под сущностью p класса POLYGON, а add_vertex экспортируемый компонент POLYGON, то его вызов сущностью p корректен. В результате выполнения в прямоугольнике появится еще одна вершина, а значит, будет создан недопустимый объект.
Корректность систем и классов
Для обсуждения проблем ковариантности и скрытия потомком нам понадобится несколько новых терминов. Будем называть классово-корректной (class-valid) систему, удовлетворяющую трем правилам описания типов, приведенным в начале лекции. Напомним их: каждая сущность имеет свой тип; тип фактического аргумента должен быть совместимым с типом формального, аналогичная ситуация с присваиванием; вызываемый компонент должен быть объявлен в своем классе и экспортирован классу, содержащему вызов.
Система называется системно-корректной (system-valid), если при ее выполнении не происходит нарушения типов.
В идеале оба понятия должны совпадать. Однако мы уже видели, что классово-корректная система в условиях наследования, ковариантности и скрытия потомком может не быть системно-корректной. Назовем такую ошибку нарушением системной корректности (system validity error).
Практический аспект
Простота проблемы создает своеобразный парадокс: пытливый новичок построит контрпример за считанные минуты, в реальной практике изо дня в день возникают ошибки классовой корректности систем, но нарушения системной корректности даже в больших, многолетних проектах возникают исключительно редко.
Однако это не позволяет игнорировать их, а потому мы приступаем к изучению трех возможных путей решения данной проблемы.
Далее мы будем затрагивать весьма тонкие и не столь часто дающие о себе знать аспекты объектного подхода. Читая книгу впервые, вы можете пропустить оставшиеся разделы этой лекции. Если вы лишь недавно занялись вопросами ОО-технологии, то лучше усвоите этот материал после изучения лекций 1-11 курса "Основы объектно-ориентированного проектирования", посвященной методологии наследования, и в особенности лекции 6 курса "Основы объектно-ориентированного проектирования", посвященной методологии наследования.
Корректность систем: первое приближение
Давайте сконцентрируемся вначале на проблеме ковариантности, более важной из двух рассматриваемых. Этой теме посвящена обширная литература, предлагающая ряд разнообразных решений.
Контравариантность и безвариантность
Контравариантность устраняет теоретические проблемы, связанные с нарушением системной корректности. Однако при этом теряется реалистичность системы типов, по этой причине рассматривать этот подход в дальнейшем нет никакой необходимости.