20.1.1. Как наследование усложняет понятие типа
Переопределенный метод не просто заменяет поле в записи, но, скорее, изменяет
поведение объекта, причем произвольным образом. Давайте рассмотрим влияние такой
замены на процесс верификации
1
.
Программист разрабатывает набор классов, определяет для каждого метода входные и
выходные условия и, проверяя их выполнение, доказывает правильность программы.
Второй программист затем создает подклассы исходных классов, переопределяя
некоторые методы. Если наша точка зрения на подклассы соответствует условиям
верификации, то для экземпляра подкласса условия также будут выполнены. Тогда мы
заменим в программе экземпляры класса экземплярами подкласса и можем надеяться, что
получившаяся программа останется правильной. Мы пришли к принципу подстановки,
обсуждавшемуся в главе 10.
В то время как обычная программистская практика и здравый смысл диктуют, что
переопределенный метод не должен слишком радикально отклоняться от родительского, в
общем случае нет гарантии, что поведение переопределенного метода будет как-либо
связано с поведением исходного метода в надклассе. (Как правило, дочерний класс и не
будет вести себя точно так же, как родительский — иначе не было бы нужды в
переопределении.) Таким образом, у нас нет, вообще говоря, уверенности, что некоторые
единые входные и выходные условия будут справедливы для обоих методов. Если одно из
этих условий нарушено, основа нашей веры в правильность программы оказывается
подорванной, и программа, вероятно, даст сбой. Типичным источником пробле
мы неоднозначности (см. главу 7) является замена программистом (возможно,
непреднамеренно) одного метода другим, для которого не сохранены некоторые важные
аспекты поведения.
Пример: пасьянс
Если мы хотим доказать правильность программы карточного пасьянса, представленной в
главе 8, то мы должны объяснить работу подпрограммы draw в классе TablePile, которая
отображает стопку карт на карточном столе. Чтобы создать изображение, подпрограмма
просто проходит в цикле по стопке карт снизу вверх, очищая поверхность под каждой
картой и перерисовывая изображение. Таким образом, нижележащие карты
прорисовываются, а затем частично стираются по мере того, как рисуется стопка карт.
Предположим, что данное приложение создано и мы обосновали — формально или
неформально — его корректность. Теперь новый программист решает, что следует
улучшить эффективность подпрограмм рисования, используя параллелизм. Поскольку
графические операции являются относительно длительными, то, когда объекту-карте
требуется отобразить себя, он попросту запускает фоновый процесс, осуществляющий
операцию рисования, и продолжает работу в параллельном режиме. Методики, подобные
1
Верифицирование — специальная техника разработки программ, подробно описанная в [Gries 1981]. При
таком подходе в программу добавляются специальные условия, оформленные в виде псевдокомментариев.
Выполнение условий считается необходимым для правильной работы программы. Они используются (явно
или неявно) программистом при разработке и отладке программы и применяются для математически
строгого доказательства правильности программы. Верификации (и в особенности их частный случай —
инвариант цикла) вместе с техникой формального доказательства правильности программ предложены
Т. Хоаром. Имеются специальные компиляторы, которые в режиме отладки превращают
псевдокомментарии во фрагменты кода, проверяющие во время выполнения правильность заданных
условий. — Примеч. перев.
PDF created with pdfFactory Pro trial version www.pdffactory.com