With ASomeType As TAnotherType Do

приводит к тому, что сам экземпляр остается неизменным, однако вызы­ваются те методы, как если бы он принадлежал к присваиваемому типу. Для несовместимых типов эта операция приводит к возбуждению исключения EInvalidCast.

Чаще всего оператор As применяется для выполнения определенных действий над параметром Sender, например, для безопасного приведения па­раметра Sender к конкретному типу:

(Sender As TControl).Visible:=True;

Напомним, что стандартный (неявный) способ приведения типа выгля­дит как:

With TAnotherType(ASoineType) Do...

От стандартного способа приведения типов использование оператора As отличается наличием проверки на совместимость типов во время выполнения операции приведения типа. Очень полезным может быть использование этого оператора в методах-обработчиках событий. Для обеспечения совместимости источник событий Sender имеет тип TObject, хотя ясно, что реально им может быть форма или любые другие компоненты.

Паскалевская форма приведения типа также допустима, но менее надеж­на из-за отсутствия проверки на совместимость типов.

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

Указатели на класс

Информация в RTTI "живет самостоятельной жизнью" и может исполь­зоваться без создания экземпляра класса. Доступ к RTTI класса вне методов класса можно получить, описав соответствующий указатель, называемый указателем (ссылкой) на класс или указателем на объектный тип (Class reference). Описывается она с помощью зарезервированных словClass OfНапример, указатель на класс TObject описан в модуле System и называется TClass:

Type TObject=Class;

TClass=Class Of TObject;

Известны также указатели TComponentClass, TControlClass и т.п.

Примечания:

• Переменные - классовые ссылки не несут в себе информации, содер­жащейся в экземпляре класса, зато содержат информацию о его типе.

• Классовая ссылка используется в следующих случаях:

• когда не известен тип создаваемого объекта на этапе компиляции;

• когда необходим вызов метода класса, чей тип не известен на этапе компиляции:

• в качестве правого операнда в операциях проверки и приведения типов (Is и As).

• Указатели на классы тоже подчиняются правилам приведения объ­ектных типов.

• Ссылки можно использовать во всех выражениях, где допустимо ис­пользование типа данных. Например, конструкторы, а также классовые процедуры и функции могут вызываться с помощью ссылки на класс.

• Указатель на класс-предок может ссылаться и на любые дочерние классы, но обратное невозможно.

Type

TMyClass=Class(TObject)

. . .

End;

TObjRef=Class Of TObject;

Var ObjRef: TObjRef;

S: String;

Begin

ObjRefs^TMyClass; // Ссылка на класс, а не на экземпляр

S:=ObjRef.ClassName; // Строка S содержит строку "IMyClass'

. . .

S:=Sender.ClassName; // Строка S содержит имя класса

• Используя классовые ссылки, можно достичь своего рода полимор­физма при создании экземпляров объектов в процессе работы программы.

Type

TMyControl=Class OfTControl;

. . .

Implementation

Function MyCreate(MyClass: TMyControl; Const MyName: String;

X, Y, W, H: Integer): TControl;

Begin

// Создаем новый объект, класс которого MyClass

Result:=MyClass.Create(Forml);

With Result Do Begin

Parent:=Fonnl; // Parent: =Forml.Panell; - на другом контейнере

Name:=MyName;

SetBounds(X. Y, W, H);

Visible:=True;

End;

End;

Begin

// Создание поля ввода в нужном месте производится следующим образом

MyCreate(TEdit, 'Editi', 10,10,100,20);

• Для динамического удаления компонентов можно воспользоваться свойствами ComponentCount и Components [Index] и оператором Is или ис­пользовать функцию FindComponent.

• For I:=0 To ComponentCount-l Do

lfComponents[I| Is TEdit Then Components|I].Free;

• FindComponent('Editl').Free;

• FindComponent(TControl(Sender).Name).Free;

Фактически Delphi использует виртуальный конструктор, создавая ком­поненты на форме после вашего щелчка по компоненту на палитре компо­нентов и форме.

ПОЛИМОРФИЗМ

Полиморфизм (или много форм) - третья и самая мощная грань ООП. Полиморфизм может быть описан как поведенческая абстракция, - возмож­ность вызывать действие или поведение по имени какого-либо конкретного экземпляра класса, не зная в точности, какая именно реализация метода при этом будет вызвана, и даже не зная в точности, к какому типу принадлежит данный объект. Тип экземпляра класса и реализация метода, который будет вызван в процессе выполнения программы, не могут быть полностью опреде­лены на фазе трансляции. Более того, процесс будет выполняться динамиче­ски - один и тот же код может работать с экземплярами разных классов.

Ключевой момент в виртуальных методах - указатели на код, хранящий­ся в VMT, а ключевой момент в полиморфизме - использование данных экземпляра класса для получения указателя на нужную таблицу VMT во время выполнения программы. Действительно, у компилятора нет возможности оп­ределить класс объекта, фактически передаваемого для обработки в процессе выполнения программы. Поэтому нужен механизм, позволяющий определить это непосредственно во время выполнения, - это называется поздним связы­ванием (late binding). Этот механизм должен быть связан с передаваемым объектом. В качестве такого механизма и служат VMT и DMT.

Таким образом, полиморфизм - это механизм, позволяющий во время

работы программы установить родительский объект равным одному из до­черних.

Для реализации полиморфизма подходят две схемы взаимоотношения класса-предшественника и производного класса:

• есть класс-предшественник, в котором объявлен один или более вир­туальных (динамических) методов. Имеется производный класс, в котором

произведено замещение одного или более виртуальных (динамических) мето­дов класса-предшественника;

• есть класс-предшественник, в котором объявлен один или более абст­рактных метода. Имеется один или более производных класса, в которых пе­реопределены абстрактный метода класса-предшественника.

Экземпляр производного класса можно присвоить переменной класса-предшественника, но обратное невыполнимо, поскольку объект производного класса больше объекта класса-предшественника, поскольку включает новые поля и/или методы. Более того, если бы это было возможно, то экземпляр класса-предшественника мог бы обращаться к полям и методам, которых у него нет.

Таким образом, полиморфизм возможен тогда, когда имеется:

• класс предшественник, определяющий один или более виртуальных методов;

• один или более производных классов, которые могут замещать эти виртуальные методы;

• переменная представителя класса, объявленная, как переменная типа класса-предшественника, но реально содержащая представителя одного из производных классов.

Type

TBaseClass=CIass // Базовый класс

FDate: TDateTime;

Function GetDate: String; Virtual; Abstract;

End;

TDateCIass=Class(TBaseClass) // Первый производный класс

Function GetDate: String; Override;

End;

TTimeCIass=Class(TBaseClass) // Второй производный класс

Function GetDate: String; Override;

• В обработчиках событий OnClick всех указанных компонентов указать FormClick.

• После запуска формы можно щелкнуть по любому компоненту, и бу­дет происходить его перемещение в другое место (или перерисовка -Repaint), которое для каждого из них будет происходить известным для него способом, иначе все компоненты были бы одинаковыми.

Если в этом примере заменить исполняемый код на:

(Sender As TControl).Hint:='Это ярлычок!';

и установить свойство ShowHint экранной формы в True, то после щелч­ка мышью по форме ярлычки будут далее появляться у всех компонентов. Это пример полиморфного присвоения. Щелчок по компоненту приводит к появлению подсказки у него.

Поскольку все используемые компоненты являются потомками от TControl, они обрабатываются одинаково, т.е. всем присваивается одинаковое значение.

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

У каждого классового типа есть своя уникальная VMT (DMT). Блок дан­ных экземпляра каждого типа содержит скрытый указатель на VMT своего класса (напомним, что VMT - это принадлежность класса, а не экземпляра или объекта). Когда транслятору нужно сгенерировать код для вызова вирту­ального метода, он сначала генерирует код для получения указателя на VMT из данных экземпляра, затем получает адрес кода нужного метода из соответ­ствующего поля VMT и далее выполняется вызов по полученному адресу.

Полиморфизм использует абстракцию и помогает ей, используя обоб­щенные процессы для управления родственными специализированными ре­акциями. За счет малого количества кода контролируется обширный набор вариантов поведения.

СВОЙСТВА (PROPERTIES)

Свойство (property) - это атрибут формы или другого компонента, кото­рый влияет либо на визуальное поведение, либо на операции, выполняемые формой или компонентами. Например, свойство Visible определяет, является ли компонент видимым. Аналогично свойство Enable определяет, доступен или нет управляющий элемент. Фактически свойство - это просто имя, свя­занное с полем напрямую или методами записи и/или чтения и наиболее ви­димая часть класса.

При использовании в этом точном техническом значении, термин обо­значает расширение концепции поля данных. Свойство является элементом определения класса и обеспечивает специальную защиту связанных с ним данных, поддерживая их автоматическую настраиваемую обработку при про­смотре и изменении значений. Другими словами, свойство не являются толь­ко данными, которые присваиваются и используются; присвоение и/или ис­пользование этих данных может привести к особым побочным эффектам. Это один из часто используемых вариантов реализации инкапсуляции.

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

Объявление свойств

Синтаксис объявления свойства класса:

Property <имя свойства>: <тип данных> [Index <целое число>} [Read <поле свойства\метод чтения>] [Write <поле свойства\метод записи>} [Stored <логическое выражение>] [Default <значение по умолчанию>|NoDefault] [DispID <целое число>][Implements <список интерфейсов>

Примечания:

• Delphi позволяет объявлять свойства следующих типов:

• простые типы, включая целые и вещественные числа, например Width, Height, символьные, перечислимые (например цвет формы, компонентов), логические (False, True) и диапазоны;

• строковые, например имена компонентов (Name), заголовки (Caption), значения

(Text):

• множества, например Borderlcons (biSystemMenu, biMinimize, biMaximize, biHelp). Размер публикуемых (Published) свойств типа множество ограничен 32 элементами (0..31). С большим числом элементов можно объявить свойство в разделе Public;

• массивы, включая многомерные, например Lines (TStrings). Нельзя объявлять та­кие свойства в разделе Published. Массивы обычно имеют встроенные редакторы для изменения значений;

• указатели;

• объекты, включая интерфейсные типы, например TFont. Объекты обычно име­ют встроенные редакторы для изменения значений. Свойства-объекты должны проис­ходить от класса TPersistenf.

• Объявление свойства никогда не приводит к резервированию памяти для хранения значений свойства, поскольку экземпляр класса хранит значе­ние свойства в одном из соответствующих своих полей.

• Свойства должны объявляться после объявления полей, можно впере­межку с методами. Главное, чтобы упоминаемые в разделах Read и Write методы были описаны ранее.

• Поля и/или методы доступа могут быть унаследованными, но види­мыми потомком.

• Свойства, как и поля, могут использоваться в качестве аргументов процедур и функций, а также участвовать в выражениях, например логиче­ских, математических. Нельзя, однако, передавать их по ссылке (как Var па­раметр), а также использовать с оператором @.

• Свойство должно иметь хотя бы один из разделов Read или Write. Ес­ли раздел Read отсутствует, то свойство доступно только для записи, на­пример, для хранения пароля, а, если отсутствует раздел Write, то свойство годится только для чтения и его нельзя модифицировать.

• Если потребности в специальных методах нет, то для доступа к по­лям можно вместо имен методов использовать имена полей. Чаще всего так делают для раздела Read.

• Методы чтения и записи рекомендуется объявлять в разделе Protected, чтобы их нельзя было неосторожно изменить, а также виртуаль­ными или динамическими, чтобы была возможность переопределить их в потомках. Методы, однако, не должны быть абстрактными (Abstract) или перегружаемыми (Overload).

• Если в разделах Read и Write указаны методы, то типы данных поля и свойства могут не совпадать. В этом случае соответствующие преобразо­вания типов должны производится в методах чтения и записи.

• Свойства, как и другие составляющие класса, наследуются потомка­ми.

• Свойства, объявляемые в разделе Automated класса, могут иметь только разделы Read, Write и DispID.

Type

TAnyClass=Class(TComponent) // Объявление нового класса

Private

FCount: Integer; // Поле для хранения значения свой­ства

Protected

Function GetCount: Integer; // Объявление метода чтения

Procedure SetCount(Value: Integer); // Объявление метода записи

Public

Property Count: Integer Read GetCount Write SetCount Default. 1;// Свойство

End;

Var AnyObject: TAnyClass; // Объявление переменной

Function TAnyClassGetCount: Integer; // Реализация метода чтения

Begin

GetCount:'=FCount; // Присвоение значения

End;

Procedure TAnyClass.SetCount(Value: Integer); // Реализация метода записи

Begin

If Value>=0 Then FCount:=Value Else FCount:=0; // С проверкой на значение

End;

Доступ к значению свойства Count осуществляется через вызовы мето­дов GetCount и SetCount. Однако обращаться напрямую к этим методам нет необходимости, поскольку в программе достаточно написать:

AnyObject.Count:=123;

AVariable:=AnyObject.Count;

Компилятор оттранслирует операторы присвоения в вызовы методов чтения и записи. Если используется прямой доступ к полю, то значения поля будут доступны с помощью операторов присвоения. Таким образом, внешне свойство выглядит в точности как обычное поле, но за всяким обращением к нему могут стоять нужные вам действия. Например, если имеется объект геометрическая фигура и его свойству "цвет" присваивается значение "белый", то произойдет немедленная перерисовка объекта в цвет, соответст­вующий значению свойства.

Важное значение имеют свойства при разработке компонентов. Разра­ботчик может менять реализацию методов, а для пользователей все остается по старому.

В простейшем случае может использоваться прямой доступ к полю. Ча­ще всего прямой доступ используется при операциях чтения значения поля.

Type

TAnyComponent=Class(TConiponent) // Объявление нового класса

Private

FCount: Integer; // Используется для хранения свой­ства

Procedure SetCount(Value: Integer); // Объявление метода записи

Public

Property Count: Integer Read FCount Write SetCount; // Объявление свойства

End;

В Delphi не предусмотрены ограничения на то, как хранить значения свойства. Однако компоненты Delphi придерживаются правила, заключающе­гося в том, что значение свойства хранится в поле, которые обычно объявля­ются в разделе Private класса.

Производные компоненты должны использовать наследуемые свойства. Они не нуждаются в прямом доступе к внутреннему хранению данных.

6.2. Объявления свойств-массивов

Объявление свойств-массивов имеет ряд особенностей как при их объ­явлении, так и при использовании.

Синтаксис объявления свойства-массива класса:

Property <имя cвoucmвa>[[Const] <индекс1>:<тип шдекса>

[; [Coast] <индекс2>:<тип индекса>]]: <тип данных>

[Read <метод чтения>]

[Write <метод записи>][;Default];

Примечания:

• Индексы свойств-массивов оформляются аналогично параметрам процедур и функции, но только в квадратных скобках.

• Идентификатор индекса свойства-массива может дополняться спе­цификатором Const.

• Команда Default отделяется от других команд объявления свойства-массива точкой с запятой и должна быть последней.

• Свойства-массивы могут иметь индексы любого стандартного типа.

• Property FieldValues[Const FieldName: String]: Variant; Default;

// Свойство строкового типа компонентов Table, Query u TSoredProc, используемых для работы с базами данных. Есть строковый индекс и у TStrings. Values.

• Function GetValue(Const Name: String): String;

Procedure SetValue(Const Name, Value: String);

Property Values[Const Name: String]; String Read GetValue Write SetValue;

• Type

ArrStr=Array[1..7] Of String[10]; // Объявление типа - массива строк

TAnyClass=Class // Объявление нового класса

Private

FArr: ArrStr; // Поле-массив Function GetIntArr(iIndex: Integer): String; //Метод чтения Function GetStrArr(sIndex: String): Integer; //Метод чтения Public

Property Plnt[ilndex: Integer]: String Read GetIntArr;

Property PStr[sIndex: String]: Integer Read GetStrArr;

End;

Доступ к полю типа массив с помощью свойства со строковым индексом производится по значению строки. Фактически ищется индекс элемента мас­сива, значение которого равно строковому индексу свойства-массива.

Function TAnyCIass.GetStrArr(sIndex: String): Integer;

Var I: Byte;

Begin

Result:=-l;

For I:=l To 7 Do IfUpperCase(FArr[I])=UpperCase(sIndex) Then Result:=I;

End;

Раздел Read

Этот раздел в объявлении свойства указывает, как получать значения свойства. Может быть указано поле (прямой доступ) или метод чтения (косвенный доступ). В последнем случае, метод чтения должен быть функци­ей, тип которой совпадает с типом свойства.

Примечания:

• Для простых свойств метод чтения должен быть функцией, возвра­щающей значение свойства.

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

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

• Метод должен быть объявлен в определении класса до ссылки на него в объявлении свойства или в видимых разделах классов-предков. Рекомендуе­мое соглашение об именовании таких методов - начинать их со словаGet.

Раздел Write

Этот раздел в объявлении свойства указывает, как присваивать свойству новое значение. Может быть указано поле (прямой доступ) или метод записи (косвенный доступ). В последнем случае, метод записи должен быть проце­дурой с параметром, тип которого совпадает с типом свойства.

Примечания:

• Для простых свойств - это единственный параметр метода записи.

• Для векторных свойств, первым параметром метода записи должен быть индекс, а вторым - новые данные свойства.

• Для свойств-массивов метод записи должен иметь на единицу больше параметров, чем размерность массива. Причем последний параметр - новые данные элементов массива, а остальные - тех же типов и в той же после­довательности, что и индексы массива.

• В любом случае параметр с данными должен передаваться либо по значению, либо как Const.

• Код реализации метода записи может включать процедуру Update, обновляющую представление компонента.

• Метод записи должен быть объявлен в определении класса до ссылки на него в объявлении свойства или в видимых разделах классов-предков. Ре­комендуемое соглашение об именовании таких методов - начинать их со словаSet.

• Если свойство типа массив использует прямой доступ к полю, то не­обходимо присваивать полю весь массив одновременно (обычно с помощью вспомогательной переменной-массива).

Некоторую особенность должен иметь раздел Write для объектных свойств:

• Класс-обладатель свойства должен обязательно включать конструктор и деструктор для выделения и освобождения памяти под корреспондирующее свойству поле того же объектного типа.

• Метод Write должен производить проверку на Nil ссылки объекта, ко­торый предполагается присвоить с помощью свойства в поле объектного типа класса-обладателя свойства, например с помощью функции Assigned:

If Assigned( Value) Then FSomeObject.Assign(Value).

• Фактически следует не присваивать экземпляр класса свойству объ­ектного типа, а копировать значения его полей, иначе будет некорректная ра­бота с экземпляром при переприсвоении значений свойству объектного типа.

Свойства-объекты должны быть потомками класса TPersistent, еслине­обходимо, чтобы их публикуемые свойства отображались в Инспекторе объ­ектов Delphi. В общем случае они могут быть потомками класса Tobject.

Рассмотрим упрощенный пример объявления и использования свойства-объекта, класс которого не является потомком класса TPersistent, поэтому не будет отображаться в инспекторе объектов. Последовательность создания свойства объекта следующая:

• Объявляем класс, который будет определять тип свойства-объекта:

TAnyClass=Class Private

FFId: String; // Поле класса

Public

Procedure Assign(0bj: TObject);

Property Fid: String Read FFId Write FFId; // Свойство класса

End;

• Объявляем класс, который будет включать свойство-объект:

TPropClass=Class Private

FProp: TAnyClass; // Поле свойства-объекта

Procedure SetPropObj(Obj: TAnyClass);

Public

Constructor Create;

Наши рекомендации