Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
class A feature {B, ...}
f...
...
делает f доступным в реализации собственных компонентов B. Потомки B, в свою очередь, имеют доступ к реализации предка, а потому они должны быть вправе обращаться ко всем доступным B возможностям, в том числе, к f.
Практические наблюдения подтверждают это теоретическое обоснование. Все, что необходимо классу, обычно требуется и его потомкам. Однако нам не хотелось бы с появлением очередного порожденного класса B возвращаться в A и расширять его предложение экспорта.
Согласно принципу Скрытия информации, а также принципу Открыт-Закрыт, разработчику A дано право решать, делать ли f доступным для B, однако, ему запрещено ограничивать свободу разработчика B. Тем самым, имеет место правило:
Правило наследования при выборочном экспорте
Выборочно экспортированный компонент доступен как самому классу, так и всем его потомкам.
Ключевые концепции
[x]. К инварианту класса автоматически добавляются инварианты его родителей.
[x]. В подходе Проектирования по Контракту наследование, переопределение и динамическое связывание приводят к идее субподрядов.
[x]. Повторное объявление подпрограммы (переопределение или создание реализации) может сохранить или ослабить предусловие, сохранить или усилить постусловие.
[x]. Повторное объявление утверждений может использовать только require else (при объединении с предусловием связкой "или") и ensure then (при объединении с постусловием связкой "и"). Применение require/ensure запрещено. В отсутствие названных предложений подпрограмма сохраняет исходные утверждения.
[x]. Универсальный класс GENERAL и допускающий настройку его наследник обеспечивают переопределяемые компоненты, представляющие общий интерес для всех создаваемых разработчиком классов. Класс NONE замыкает решетку наследования снизу.
[x]. Заморозив компонент, можно гарантировать его вечную семантическую уникальность.
[x]. Ограниченная универсальность дает возможность использовать только родовые параметры со специфическими свойствами.
[x]. Попытка присваивания позволяет динамически проверить, принадлежит ли объект ожидаемому типу. Эта операция не должна использоваться как замена динамического связывания.
[x]. Потомок вправе переопределять тип любой сущности (атрибута, результата функции, формального параметра подпрограммы). Повторное определение должно быть ковариантным - заменять исходные типы соответствующими, согласуясь с требованиями потомка.
[x]. Закрепленные объявления (like anchor) - это важная часть системы типов, облегчающая применение ковариантной типизации и позволяющая отказаться от избыточных повторных объявлений.
[x]. Наследование и скрытие информации - это независимые механизмы. Потомки могут скрывать экспортированные компоненты и экспортировать скрытые компоненты.
[x]. Компонент, доступный самому классу, доступен и его потомкам.
Библиографические замечания
Иную точку зрения на взаимосвязь наследования и скрытия информации см. в [Snyder 1986].
Упражнения
У16.1 Наследование: простота и эффективность
Перепишите и упростите ранее созданную реализацию защищенного стека, сделав класс STACK3 потомком, а не клиентом STACK, чтобы избежать излишних обходных путей. (Подсказка: см. правила взаимодействия наследования и скрытия информации.)
У16.2 Векторы
Напишите класс VECTOR, представляющий числовые вектора (кольцо) с обычными математическими операциями. Сам класс рекурсивно должен относиться к численному типу, допуская вектора векторов. Возможно, для этого вам придется самостоятельно дописать класс NUMERIC (или воспользоваться готовым из [M 1994a]).
У16.3 Экстракт?
В случае, когда x1 имеет тип X, y1 имеет тип Y, и Y является потомком X, оператор y1 := x1 будет недопустимым. Однако полезным мог бы показаться универсальный компонент extract, такой, что y1.extract (x1) копирует значения полей объекта x1 в соответствующие поля объекта y1 при условии, что ни в одной из этих ссылок не содержится Void.
Объясните, почему компонент extract стоит отвергнуть. (Подсказка: обратитесь к вопросам корректности, в частности, к понятию инварианта.) Выясните, можно ли спроектировать удовлетворительный механизм, решающий эту задачу каким-то иным способом.
Лекция 17. Типизация
Эффективное применение объектной технологии требует четкого описания в тексте системы типов всех объектов, с которыми она работает на этапе выполнения. Это правило, известное как статическая типизация (static typing), делает наше ПО: более надежным, позволяя компилятору и другим инструментальным средствам устранять несоответствия прежде, чем они смогут нанести вред; более понятным, обеспечивая точной информацией читателей: авторов клиентских систем и тех, кто будет сопровождать систему; более эффективным, поскольку информация о типах данных позволит компилятору сгенерировать оптимальный код. Хотя вопросами типизации данных активно занимались и вне объектной среды, да и сама статическая типизация применяется в языках, не поддерживающих ООП, особенно ярко эти идеи проявили себя именно при объектном подходе, во многом основанном на понятии типа, которое, сливаясь с понятием модуля, образует базовую ОО-конструкцию - класс.
Проблема типизации
Эффективное применение объектной технологии требует четкого описания в тексте системы типов всех объектов, с которыми она работает на этапе выполнения. Это правило, известное как статическая типизация (static typing), делает наше ПО:
[x]. более надежным, позволяя компилятору и другим инструментальным средствам устранять несоответствия прежде, чем они смогут нанести вред;
[x]. более понятным, обеспечивая точной информацией читателей: авторов клиентских систем и тех, кто будет сопровождать систему;
[x]. более эффективным, поскольку информация о типах данных позволит компилятору сгенерировать оптимальный код.
Хотя вопросами типизации данных активно занимались и вне объектной среды, да и сама статическая типизация применяется в языках, не поддерживающих ООП, особенно ярко эти идеи проявили себя именно при объектном подходе, во многом основанном на понятии типа, которое, сливаясь с понятием модуля, образует базовую ОО-конструкцию - класс.
О типизации при ОО-разработке можно сказать одно: эта задача проста в своей постановке, но решить ее подчас нелегко.
Базисная конструкция
Простота типизации в ОО-подходе есть следствие простоты объектной вычислительной модели. Опуская детали, можно сказать, что при выполнении ОО-системы происходят события только одного рода - вызов компонента (feature call):
x.f (arg)
означающий выполнение операции f над объектом, присоединенным к x, с передачей аргумента arg (возможно несколько аргументов или ни одного вообще). Программисты Smalltalk говорят в этом случае о "передаче объекту x сообщения f с аргументом arg", но это - лишь отличие в терминологии, а потому оно несущественно.
То, что все основано на этой Базисной Конструкции (Basic Construct), объясняет частично ощущение красоты ОО-идей.
Из Базисной Конструкции следуют и те ненормальные ситуации, которые могут возникнуть в процессе выполнения:
Определение: нарушение типа
Нарушение типа в период выполнения или, для краткости, просто нарушение типа (type violation) возникает в момент вызова x.f (arg), где x присоединен к объекту OBJ, если либо:
[x]. не существует компонента, соответствующего f и применимого к OBJ,
[x]. такой компонент имеется, однако, аргумент arg для него недопустим.
Проблема типизации - избегать таких ситуаций:
Проблема типизации ОО-систем
Когда мы обнаруживаем, что при выполнении ОО-системы может произойти нарушение типа?