Объектно-ориентированное программирование
ть новые объекты,
используя свойства прежних, дополняя или изменяя их. Объект-наследник
получает все поля и методы «родителя», к которым он может добавить свои
собственные поля и методы или заменить («перекрыть») их своими методами.
Пример описания объекта-наследника даётся ниже:
Tipe
Point = object(Location)
Visible: Boolean;
Procedure Int(IntX, IntY: Integer);
Procedure Show;
Procedure Hide;
Function IsVisible: Boolean;
Procedure MoveTo(NewX, NewY: Integer);
End;
Наследником здесь является объект Point, описывающий графическую
точку, а родителем – объект Location. Наследник не содержит описание полей
и методов родителя. Имя последнего указывается в круглых скобках после
слова object. Из методов наследника можно вызывать методы родителя. Для
создания наследника не требуется иметь исходный текст объекта родителя.
Объект-родитель может быть уже в составе оттранслированного модуля.
В чём привлекательность наследования? Если некий объект был уже
определён и отлажен, он может быть использован и в других программах. При
этом может оказаться, что новая задача отличается от предыдущей, и
возникает необходимость некоторой модификации как данных, так и методов их
обработки. Программисту приходится решать дилемму – создания объектов
заново или использовать результаты предыдущей работы, применяя механизм
наследования. Первый путь менее эффективен, так как требует дополнительных
затрат времени на отладку и тестирование. Во втором случае часть этой
работы оказывается выполненной, что сокращает время на разработку новой
программы. Программист при этом может и не знать деталей реализации объекта-
родителя.
В нашем примере к объекту, связанному с определением положения
графического элемента, просто добавилось новое поле, описывающее признак
видимости графической точки, и несколько новых методов, связанных с режимом
отображения точки и её преобразованиями.
6. Виртуальные методы
Наследование позволяет создавать иерархические, связанные отношениями
подчинения, структуры данных. Следует, однако, заметить, что при
использовании этой возможности могут возникнуть проблемы. Предположим, что
в нашей графической программе необходимо определить объект Circle, который
является потомком другого объекта Point:
Type
Circle = object (point)
Radius: Integer;
Procedure Show;
Procedure Hide;
Procedure Expand(ExpandBy: Integer);
Procedure Contact(ContactBy: Integer);
End;
Новый объект Circle соответствует окружности. Поскольку свойства
окружности отличаются от свойств точки, в объекте-наследнике придется
изменять процедуры Show и Hide, которые отображают окружность и удаляют её
изображение с экрана. Может оказаться, что метод Init (см. предыдущий
пример) объекта Circle, унаследованный от объекта Point, также использует
методы Show и Hide, впредь во время трансляции объекта Point использует
ссылки на старые методы. Очевидно в объекте Circle они работать не будут.
Можно, конечно, попытаться «перекрыть» метод Init. Чтобы это сделать, нам
придётся полностью воспроизвести текст метода. Это усложни работу, да и не
всегда возможно, поскольку исходного текста программы может не оказаться
под рукой (если объект-родитель уже находиться в оттранслированном модуле).
Для решения этой проблемы используется виртуальный метод. Связь между
виртуальным методом и вызывающими их процедурами устанавливается не во
время трансляции (это называется ранним связанием), а во время выполнения
программы (позднее связание.
Чтобы использовать виртуальный метод, необходимо в описании объекта
после заголовка метода добавить ключевое слово virtual. Заголовки
виртуальных методов родителя и наследника должны в точности совпадать.
Инициализация экземпляра объекта, имеющего виртуальные методы, должна
выполняться с помощью специального метода – конструктора. Конструктор
обычно присваивает полям объекта начальные значения и выполняет другие
действия по инициализации объекта. В заголовке метода-конструктора слово
procedure заменяется словом constructor. Действия обратные действиям
конструктора, выполняет ещё один специальный метод – деструктор. Он
описывается словом destructor.
Конструктор выполняет действия по подготовке позднего связывания. Эти
действия заключаются в создании указателя на таблицу виртуальных методов,
которая в дальнейшем используется для поиска методов. Таблица содержит
адреса всех виртуальных методов. При вызове виртуального метода по его
имени определяется адрес, а затем по этому адресу передается управление.
У каждого объектного типа имеется своя собственная таблица виртуальных
методов, что позволяет одному и тому же оператору вызывать разные
процедуры. Если имеется несколько экземпляров объектов одного типа, то
недостаточно вызвать конструктор для одного из них, а затем просто
скопировать этот экземпляр во все остальные. Каждый объект должен иметь
свой собственный конструктор, который вызывается для каждого экземпляра. В
противном случае возможен сбой в работе программы.
Заметим, что конструктор или деструктор, могут быть «пустыми», то есть
не содержать операторов. Весь необходимый код в этом случае создается при
трансляции ключевых слов construct и destruct.
7. Динамическое создание объектов
Переменные объектного типа могут быть динамическими, то есть
размещаться в памяти только во время их использования. Для работы с
динамическими объектами используются расширенный синтаксис процедур New и
Dispose. Обе процедуры в этом случае содержат в качестве второго параметра
вызов конструктора или деструктора для выделения или освобождения памяти
переменной объектного типа:
New(P, Construct)
или
Dispose(P, Destruct)
Где P – указатель на переменную объектного типа, а Construct или
Destruct – конструктор и деструктор этого типа.
Действие процедуры New в случае расширенного синтаксиса равносильно
действию следующей пары операторов:
New(P);
P^.Construct;
Эквивалентом Dispose является следующее:
P^Dispose;
Dispose(P)
Применение расширенного синтаксиса не только улучшает читаемость
исходного кода, но и генерирует более короткий и эффективный исполняемый
код.
8. Полиморфизм
Полиморфизм заключается в том, что одно и то же имя может
соответствовать различным действиям в зависимости от типа объекта. В тех
примерах, которые рассматривались ранее, полиморфизм проявлялся в том, что
метод Init действовал по-разному в зависимости от того, является объект
точкой или окружностью. Полиморфизм напрямую связан с механизмом позднего
связывания. Решение о том, какая операция должна быть выполнена в
конкретной ситуации, принимается во время выполнения программы.
Следующий вопрос, связанный с использованием объектов, заключается в
совместимости объектных типов. Полезно знать следующее. Наследник сохраняет
свойства совместимости с другими объектами своего родителя. В правой части
оператора присваивания вместо типов родителя можно использовать типы
наследника, но не наоборот. Таким образом, в нашем примере допустимы
присваивания:
Var
Alocation : Location;
Apoin : Point;
Acircle : Circle;
Alocation :=Apoint
Apoint := Acrcle;
Alocation := Acircle;
Дело в том, что наследник может быть более сложным объектом,
содержащим поля и методы, поэтому присваиваемые значения экземпляра объекта-
родителя экземпляру объекта-наследника может оставить некоторые поля
неопределёнными и, следовательно, представляет потенциальную опасность. При
выполнении оператора присвоения копируются только те поля данных, которые
являются общими для обоих типов.
-----------------------
[1] Выполняется на языке Turbo Pascal, начиная с версии 5.0. Далее все
примеры даны для выполнения на этом языке программирования.
| | скачать работу |
Объектно-ориентированное программирование |