4.8 Выводы

Предыдущая  Содержание  Следующая V*D*V

Объекты суперкласса и подкласса похожи, но не идентичны в поведении. Объекты подкласса обычно имеют более сложное состояние и больше методов — они представляют собой специализированные версии объектов суперкласса.

Представление объекта подкласса начинаем с копии представления суперкласса объекта, то есть объект подкласса представлен путем добавления компонентов к концу объекта суперкласса.

Подкласс наследует методы суперкласса: поскольку начало объекта подкласса выглядит как суперкласс объекта, можно выполнить повышающее приведение и рассматривать указатель на объект подкласса как указатель на объект суперкласса, который можно передать методу суперкласса. Чтобы избежать явных преобразований, мы декларируем все параметры методов с void * в качестве неопределённых указателей.

Наследование можно рассматривать как рудиментальную форму полиморфизма: метод суперкласс принимает объекты различных типов, а именно объектов своего класса и всех подклассов. Тем не менее, так как все объекты представляются в качестве объектов суперкласса, такой метод воздействует только на компонент суперкласса каждого объекта, и вероятно, следовательно, не воздействует на объекты из разных классов по-разному.

Динамически скомпонованные методы могут быть унаследованы от суперкласса или перезаписаны в подклассе — это определяется для подкласса какими-нибудь указателями на функции, добавленными в описание типа. Поэтому если для объекта вызывается динамически скомпонованный метод, всегда достигается метод, принадлежащий правильному классу объекта, даже если для этого указателя было выполнено повышающее приведение к какому-то суперклассу. Если динамически скомпонованный метод наследуется, он может воздействовать только на компонент суперкласса объекта подкласса, потому что не знает о существовании подкласса. Если метод перезаписывается, версия подкласса может обращаться ко всему объекту и может даже вызвать соответствующий ему метод суперкласса с помощью явного использования описания типа суперкласса.

В частности, конструкторы должны вызывать конструкторы суперклассов по направлению к самому первому предку, так, чтобы каждый конструктор подкласса имел дело только с своими собственными расширениями класса к представлению его суперкласса. Каждый деструктор подкласса должен удалить ресурсы подкласса, а затем вызвать деструктор суперкласса и так далее до самого первого предка. Конструирование происходит от предка к конечному подклассу, разрушение происходит в обратном порядке.

Наша стратегия имеет недостаток: вообще-то не следует вызывать динамически скомпонованные методы из конструктора, так как объект может не быть инициализирован полностью. new() вставляет окончательное описание типа в объект до того, как вызывается конструктор. Поэтому, если конструктор вызывает динамически скомпонованный метод для объекта, он не обязательно достигнет данный метод того же класса, что и конструктор. Безопасной техникой для конструктора было бы вызывать такой метод по его внутреннему имени в том же классе, то есть для точек вызывать Points_draw() вместо draw().

Поощряя сокрытие информации, мы реализуем класс с помощью трёх файлов. Файл интерфейса содержит описание абстрактного типа данных, файл представления содержит структуру объекта, а файл реализации содержит код методов и инициализирует описание типа. Файл интерфейса подключает файл интерфейса суперкласса и подключается для реализации, а также в любое приложение. Файл представления подключает в файл представления суперкласса и подключается только для реализации.

В подклассе не должно быть ссылок непосредственно на компоненты суперкласса. Вместо этого можно либо предоставлять статически скомпонованный доступ и, возможно, методы модификации для каждого компонента, или можно добавить подходящие макросы в файл представления суперкласса. Функциональное обозначение делает его гораздо более простым для использования текстовым редактором или отладчиком для поиска возможной утечки информации или повреждения данных.

 

Предыдущая  Содержание  Следующая