КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Примечание. Будьте внимательны: попытка перекрытия с директивой не override, a virtual или dynamic приведет на самом деле к созданию нового одноименного метода.Будьте внимательны: попытка перекрытия с директивой не override, a virtual или dynamic приведет на самом деле к созданию нового одноименного метода. Перегрузка методов Есть еще одна, совершенно особенная разновидность методов — перегружаемые. Эту категорию методов нельзя назвать антагонистом двух предыдущих: и статические, и виртуальные, и динамические методы могут быть перегружаемыми. Перегрузка методов нужна, чтобы произвести одинаковые или похожие действия с разнотипными данными. Рассмотрим немного измененный пример, иллюстрирующий статические методы: type TlstObj = class FExtData : Extended; procedure SetData(AValue: Extended); end; T2ndObj = class(TlstObj) FIntData : Integer; procedure SetData(AValue: Integer); end; var Tl: TlstObj; T2 : T2ndObj; В этом случае попытка вызова из объекта Т2 методов ... Т2.SetData (1.0); Т2.SetData(1); ... вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри Т2 статический метод с параметром типа extended перекрыт, и он его "не признает". Где же выход из сложившегося положения? Переименовать один из методов, например создать SetlntegerData и SetExtendedData? Можно, но если методов не два, а, скажем, сто, моментально возникнет путаница. Сделать методы виртуальными? Нельзя, поскольку тип и количество параметров в одноименных виртуальных методах должны в точности совпадать. Теперь для этого существуют перегружаемые методы, объявляемые при помощи директивы overload: type TlstObj = class FExtData : Extended; procedure SetData(AValue: Extended);overload; end; T2ndObj = class(TlstObj) FIntData : Integer; procedure SetData(AValue: Integer); overload; end; Объявив метод SetData перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или с плавающей точкой) и в зависимости от этого подставит вызов соответствующего метода: для целочисленных данных — метод объекта T2ndobj, для данных с плавающей точкой — метод объекта Tistobj. Можно перегрузить и виртуальный (динамический) метод. Надо только в этом случае добавить директиву reintroduce: type TlstObj = class FExtData : Extended; procedure SetData(AValue: Extended); overload; virtual; end; T2ndObj = class(TlstObj) FIntData : Integer; procedure SetData(AValue: Integer); reintroduce; overload; end; На перегрузку методов накладывается ограничение — нельзя перегружать методы, находящиеся в области видимости published, т. е. те, которые будут использоваться в Инспекторе объектов. Области видимости При описании нового класса важен разумный компромисс. С одной стороны, требуется скрыть от других методы и поля, представляющие собой внутреннее устройство класса (для этого и придуманы свойства). Маловажные детали на уровне пользователя объекта будут бесполезны и только помешают целостности восприятия. С другой стороны, если слишком ограничить того, кто будет порождать классы-потомки, и не обеспечить ему достаточный набор инструментальных средств и свободу маневра, то он и не станет использовать ваш класс. В модели объектов языка Object Pascal существует механизм доступа к составным частям объекта, определяющий области, где ими можно пользоваться (области видимости). Поля и методы могут относиться к четырем группам (секциям), отличающимся областями видимости. Методы и свойства могут быть общими (секция public), личными (секция private), защищенными (секция protected) и опубликованными (секция published). Есть еще и пятая группа, automated, она ранее использовалась для создания объектов СОМ; теперь она присутствует в языке только для обратной совместимости с программами на Delphi версий 3—5. Области видимости, определяемые первыми тремя директивами, таковы.
Рассмотрим пример, иллюстрирующий три варианта областей видимости. Листинг 1.1.Пример задания областей видимости методов unit First; | unit Second; interface | interface | uses First; type | type TFirstObj = class | TSecondObj =class(TFirstObj} private | procedure Method4; procedure Methodl; | end; protected | procedure Method2; | public | procedure Methods; | end; | procedure TestProcl; | procedure TestProc2; implementation | implementation uses dialogs; | varAFirstObj :TFirstObj; var AFirstObj: TFirstObj;|ASecondObj: TSecondObj; procedure TestProcl; | procedure TSecondObj.Method4; begin | begin AFirstCbj := TFirstObj.Create; | Methodl; {недопустимо - AFirstObj.Methodl;(допустимо)| |произойдет ошибка компиляции} AFirstObj.Method2; {допустимо}| Method2; {допустимо} AFirstObj.MethodS; {допустимо}| Methods,- {допустимо} AFirstObj.Free; | end; end; | procedure TestProc2; procedure TFirstObj.Methodl; | begin begin |AFirstObj:=TFirstObj.Create; ShowMessage('1'); |AFirstObj.Methodl;{недопустимо} end; |AFirstObj.Method2;{недопустимо} procedure TFirstObj.Method2; |AFirstObj.Method3;{допустимо} begin |AFirstObj.Free; ShowMessage('2'); Methodl; |ASecondCbj:= TSecondObj.Create; end; |ASecondObj.Methodl;{недопустимо} procedureTFirstObj.Method3; |ASecondObj.Method2;{допустимо} begin |ASecondObj.MethodS;{допустимо} ShowMessage('3'); |ASecondObj.Free; Method2; | end; end; |end. end. | Если к этому примеру добавить модуль Third и попробовать вызвать методы классов TFirstObj и TSecondObj оттуда, то к числу недоступных будет отнесен и Method2 — он доступен только в том модуле, в котором описан. Наконец, область видимости, определяемая четвертой директивой — published, имеет особое значение для интерфейса визуального проектирования Delphi. В этой секции должны быть собраны те свойства объекта, которые будут видны не только во время исполнения приложения, но и из среды разработки. Публиковать можно свойства большинства типов, за исключением старого типа real (теперь он называется rеаl48), свойств типа "массив" и некоторых других. Все свойства компонентов, доступные через Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как и public. Три области видимости — private, protected, public — как бы упорядочены по возрастанию видимости методов. В классах-потомках можно повысить видимость методов и свойств, но не понизить ее. При описании дочернего класса можно переносить методы и свойства из одной сферы видимости в другую, не переписывая их заново и даже не описывая — достаточно упомянуть о нем в другом месте: type TFirstObj = class private FNumber: Integer; protected property Number: Integer read: FNumber; end; ... TSecondObj = class(TFirstObj) published property Number; end; Если какое-либо свойство объекта из состава VCL принадлежит к области public, вернуть его в private невозможно. Напротив, обратная процедура широко практикуется в Delphi. У многих компонентов (например, TEdit) есть предок (в данном случае TCustomEdit), который отличается только отсутствием опубликованных свойств. Так что, если вы хотите создать новый редактирующий компонент, порождайте его на базе TCustomEdit и публикуйте только те свойства, которые считаете нужными. Разумеется, если вы поместили свойство в область private, "достать" его оттуда в потомках возможности уже нет. Объект изнутри
Теперь, когда мы разобрались с основными определениями и механизмами ООП, настало время более подробно изучить, что представляет собой объект и как он работает. Ясно, что каждый экземпляр класса содержит отдельную копию всех его полей. Ясно, что где-то в его недрах есть указатели на таблицу виртуальных методов и таблицу динамических методов. А что еще там имеется? И как происходит вызов методов? Вернемся к примеру из разд. "Полиморфизм" данной главы: type TFirstClass = class FMyFieldl: Integer; FMyField2: Longint; procedure StatMethod; procedure VirtMethodl; virtual; procedure VirtMethod2; virtual; procedure DynaMethodl; dynamic; procedure DynaMethod2; dynamic; end; TSecondClass = class(TMyObject) procedure StatMethod; procedure VirtMethodl; override; procedure DynaMethodl; override; end; Objl: TFirstClass; Obj2: TSecondClass; На рис. 1.1 показано, как будет выглядеть внутренняя структура рассмотренных в нем объектов. Первое поле каждого экземпляра того или иного объекта содержит указатель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов. Напомним, что она содержит адреса всех виртуальных методов класса, включая унаследованные от предков. Длина таблиц VMT объектов Оbj1 и Obj2 одинакова— по два элемента (8 байт). Перед таблицей виртуальных методов расположена специальная структура, содержащая дополнительную служебную информацию. В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, имя класса и т. д. На рис. 1.1 она показана одним блоком, а ее содержимое расшифровано ниже. Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат — в начале слово, содержащее количество элементов таблицы; затем — слова, соответствующие индексам методов. Нумерация индексов начинается с —1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Обратите внимание, что DMT объекта Оbj1 состоит из двух элементов, Obj2 — из одного, соответствующего перекрытому методу DynaMethodl. В случае вызова Qbj2.DynaMethod2 индекс не будет найден в таблице DMT Obj2, и произойдет обращение к DMT Оbj1. Именно так экономится память при использовании динамических методов. В языке Object Pascal определены два оператора — is и as, неявно обращающиеся к таблице динамических методов. Оператор is предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Рис. 1.1.Внутренняя структура объектов Objl и Obj2 Выражение вида: AnObject is TObjectType принимает значение True, только если объект AnObject совместим по присваиванию с классом TObjectType, т. е. является объектом этого класса или одного из классов, порожденных от него. Кстати, определенная проверка происходит еще при компиляции: если формально объект и класс несовместимы, то компилятор выдаст ошибку в этом операторе. Оператор as введен в язык специально для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу: with ASomeObject as TAnotherType do... От стандартного способа приведения типов с помощью конструкции TAnotherType (ASomeObject) использование оператора as отличается наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации EinvalidCast (см. гл. 4). После применения оператора as сам объект остается неизменным, но вызываются те его методы, которые соответствуют присваиваемому классу. Очень полезным может быть оператор as в методах-обработчиках событий. Для обеспечения совместимости в 99% случаев источник события sender имеет тип TObject, хотя в тех же 99% случаев им является форма или другие компоненты. Поэтому, чтобы иметь возможность пользоваться их свойствами, применяют оператор аs: (Sender as TControl).Caption := "Thanks!"; Вся информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает резонный вопрос: а нельзя ли получить доступ к ней, не создавая экземпляр объекта? Да, можно. Доступ к информации класса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс TObject описан в модуле SYSTEM.PAS и называется Tclass: type TObject = class; TClass = class of TObject; Аналогичные указатели уже описаны и для других важных классов. Вы можете использовать в своей программе TComponentClass, TControlClass и т. п. Указатели на классы тоже подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы; обратное невозможно: type TFirst = class .. end; TSecond = class(TFirst) ... end; TFirstClass = class of TFirst; TSecondClass = class of TSecond; var AFirst : TFirstClass; ASecond : TSecondClass; begin AFirst := TSecond; {допустимо} ASecond := TFirst; {недопустимо} end. С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта — с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное слово class: type TMyObject = class(TObject) class function GetSize: string; end; var MyObj ect: TMyObj ect; AString: string; begin AString := TMyObject.GetSize; MyObject := TMyObject.Create; AString := MyObject.GetSize; end. Разумеется, методы класса не могут использовать значения, содержащиеся в полях класса: ведь экземпляра-то не существует. Возникает вопрос: для чего нужны такие методы? Важнейшие методы класса определены в самом TObject: они как раз и позволяют, не углубляясь во внутреннюю структуру класса, извлечь оттуда практически всю необходимую информацию.
Резюме В этой главе рассмотрены основы объектно-ориентированного программирования в Delphi. Объект обладает свойствами и методами, которые позволяют изменять значения свойств. Знание основ ООП необходимо для изучения всех глав не только этой части, но и всех последующих. Ведь компоненты Delphi — это объекты, размешенные в специальной библиотеке VCL. А ни одна глава этой книги не обходится без описания возможностей тех или иных компонентов. Рассмотренные в данной главе возможности объектов используются при создании исходного кода приложений Delphi.
Тема 2
|