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;