Выход из положения есть. Если мы всерьез готовы использовать закрепление объявлений как единственный механизм ковариантности, то избавиться от нарушений системной корректности можно, полностью запретив полиморфизм закрепленных сущностей. Это потребует изменения в языке: введем новое ключевое слово anchor (эта гипотетическая конструкция нужна нам исключительно для того, чтобы использовать ее в данном обсуждении):
anchor s: SKIER
Разрешим объявления вида like s лишь тогда, когда
В исходном варианте правила существовало понятие опорно- эквивалентных элементов. При новом подходе опорно-эквивалентными должны быть как правая, так и левая часть любого присваивания, в котором участвует опорная или закрепленная сущность. |
При таком подходе мы устраняем из языка возможность переопределения типа любых аргументов подпрограммы. Помимо этого, мы могли запретить переопределять тип результата, но в этом нет необходимости. Возможность переопределения типа атрибутов, конечно же, сохраняется. Все переопределения типов аргументов теперь будут выполняться неявно через механизм закрепления, инициируемый ковариантностью. Там, где при прежнем подходе класс
r (u: Y) ...
тогда как у класса
r (u: X) ...
где
r (u: like your_anchor) ...
Остается только в классе
Это решение проблемы ковариантности - полиморфизма будем называть подходом Закрепления (Anchoring). Более аккуратно следовало бы говорить: 'Ковариация только через Закрепление'. Свойства подхода привлекательны:
[x]. Закрепление основано на идее строгого разделения ковариантных и потенциально полиморфных (или, для краткости, полиморфных) элементов. Все сущности, объявленные как
[x]. Это простое и элегантное решение нетрудно объяснить даже начинающим.
[x]. Оно полностью устраняет возможность нарушения системной корректности в ковариантно построенных системах.
[x]. Оно сохраняет заложенную выше концептуальную основу, в том числе понятия ограниченной и неограниченной универсальности. (В итоге это решение, по-моему, предпочтительнее типовых переменных, подменяющих собой механизмы ковариантности и универсальности, предназначенных для решения разных практических задач.)
[x]. Оно требует незначительного изменения языка, - добавляя одно ключевое слово, отраженное в правиле соответствия, - и не связано с ощутимыми трудностями в реализации.
[x]. Оно реалистично (по крайней мере, теоретически): любую ранее возможную систему можно переписать, заменив ковариантные переопределения закрепленными повторными объявлениями. Правда, некоторые присоединения в результате станут неверными, но они соответствуют случаям, которые могут привести к нарушениям типов, а потому их следует заменить попытками присваивания и разобраться в ситуации во время выполнения.
Казалось бы, дискуссию можно на этом закончить. Так почему же подход Закрепления не полностью нас устраивает? Прежде всего, мы еще не касались проблемы скрытия потомком. Кроме этого, основной причиной продолжения дискуссии является проблема, уже высказанная при кратком упоминании типовых переменных. Раздел сфер влияния на полиморфную и ковариантную часть, чем-то похож на результат Ялтинской конференции. Он предполагает, что разработчик класса обладает незаурядной интуицией, что он в состоянии для каждой введенной им сущности, в частности для каждого аргумента раз и навсегда выбрать одну из двух возможностей:
[x]. Сущность является потенциально полиморфной: сейчас или позднее она (посредством передачи параметров или путем присваивания) может быть присоединена к объекту, чей тип отличается от объявленного. Исходный тип сущности не сможет изменить ни один потомок класса.
[x]. Сущность является субъектом переопределения типов, то есть она либо закреплена, либо сама является опорным элементом.
Но как разработчик может все это предвидеть? Вся привлекательность ОО-метода во многом выраженная в принципе Открыт-Закрыт как раз и связана с возможностью изменений, которые мы вправе внести в ранее сделанную работу, а также с тем, что разработчик универсальных решений не должен обладать бесконечной мудростью, понимая, как его продукт смогут адаптировать к своим нуждам потомки.
При таком подходе переопределение типов и скрытие потомком - своего рода 'предохранительный клапан', дающий возможность повторно использовать существующий класс, почти пригодный для достижения наших целей:
[x]. Прибегнув к переопределению типов, мы можем менять объявления в порожденном классе, не затрагивая оригинал. При этом чисто ковариантное решение потребует правки оригинала путем описанных преобразований.
[x]. Скрытие потомком защита от многих неудач при создании класса. Можно критиковать проект, в котором