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

Класс можно объявить внутри блока type, используя синтаксис:

type

ИмяКласса = class ( ИмяРодительскогоКласса)

<Определение класса>

end;

Рассмотрим примеры:

type

TRectangle = class (TObject)

. . .

end;

TDate = class (TNumber)

. . .

end;

За словом class в круглых скобках может указываться имя родительского класса.

Объявления классов определяют три характеристики объектов:

1) Данные объекта. Это атрибуты исходных данных, называемые полями. Они содержатся в каждом объекте.

2) Методы объекта. Их называют операциями, функциями - элементами или действиями в других языках программирования.

3) Свойства объектов. Это высокоуровневые атрибуты данных, которые тесно связаны с соответствующими методами доступа к данным.

Интерфейс класса - это набор всех элементов описания класса - полей, методов и свойств. Эти элементы иногда называют особенностями класса.

 
 

1.11.4 Наследование

Классы могут быть связаны между собой разными способами. Одна из основных концепций объектного программирования - это понятие наследования классов. Оно устанавливает между двумя классами отношения “родитель - потомок”.

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

НЕ нашли? Не то? Что вы ищете?

Предок - это класс, предоставляющий свои возможности и характеристики другим классам через механизм наследования.

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

Непосредственный предок класса, от которого данный класс прямо происходит, называется родителем.

Указатель родительского класса, размещаемый в скобках за ключевым словом class, не является обязательным. Если он не приведён, то родителем предполагается класс TObject.

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

Концептуально (концепция - система взглядов, ведущая мысль) каждый представитель класса - потомка является также представителем родительского класса.

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

1.11.5 Объектные поля

В одном отношении объект ( тип class ) подобен записи record: объект может считаться набором полей данных, или атрибутов ( от латинского attributum - данное, предписанное ; существенный признак, постоянное свойство чего - либо, неотъемлемая принадлежность предмета ), сгруппированных вместе под одним именем. Наибольшее отличие между записями и классами состоит в том, что записи не могут наследоваться одна от другой и не могут иметь присоединенных процедур. Только классы могут входить в систему наследования. Только классы определяют поля и связанные с ними подпрограммы.

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

Пример:

type

TColorRectangle = class

Left, Top, Right, Bottom: Integer;

Color: TColor;

end;

Рекомендуется использовать тип - класс в качестве основного типа при группировании связанных атрибутов данных. Не следует использовать тип-запись для группирования связанных атрибутов в логически целостные элементы - объекты являются более мощным средством.

1.11.6 Объявление переменных – экземпляров класса.

Простое объявление объектного типа недостаточно для присваивания значений его полям или какого - либо использования. При объявлении типа не выделяется памяти. В отличие от случая с классами при использовании экземпляров записи надо просто ввести переменную этого типа для действительного резервирования области памяти для экземпляра.

Простое объявление переменной классового типа ещё недостаточно для резервирования памяти под объект. Объекты, в отличие от объявленных в блоке var переменных, являются динамическими образованиями.

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

Переменная классового типа не является экземпляром класса - это просто ссылка на экземпляр. Она должна быть инициализирована во время выполнения посредством вызова конструктора.

Тип записи можно объявить следующим образом:

type

TRecordТype = record

Field1: Type1;

Field2: Type2;

Field3: Type3;

end;

Для действительного выделения памяти под запись надо объявить переменную описанного типа:

var

MyRecord: TRecordType;

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

Для объектов ситуация другая. Не только объявление класса объектов, но и введение переменных этого типа недостаточно для получения действительных экземпляров. Надо выполнить дополнительный шаг для инициализации переменной.

Пример:

1: type

2: TColorRect = class

3: FLeft, FTop, FRight, FBottom: Integer;

4: FColor : TColor;

5: end;

6:

7: var

8: ARect: TColorRect;

9:

10: begin

11: . . .

12: end;

TColor - стандартный тип, предоставляемый модулем Classes для обозначения цвета простым числом размера LongInt (32 бита).

Объявление в строке 8 не создаёт экземпляра класса TColorRect. Если в строке 11 попытаться обратиться к полю этого объекта

ARect. FLeft :=10;

то попытка будет неудачной. Появится ошибка выполнения. Переменная ARect - это ссылочная переменная, и она не инициализирована правильным экземпляром. Следовательно, её нельзя использовать для ссылок на поля или методы.

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

Экземпляры объекта хранятся в памяти динамически, т. е. выделение памяти происходит во время выполнения программы. Только небольшое количество памяти статически выделяется объектной ссылке при объявлении переменной типа class.

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

1.11.7 Обращение к элементам объекта

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

Пример:

type

TCircle = class

X, Y,Radius: Integer;

. . .

constructor Create;

procedure Move (NewX, NewY: Integer);

procedure Scale (By: Integer);

end;

var

ACircle: TCircle;

begin

. . .

ACircle := TCircle. Create;

. . .

ACircle. X := 10;

ACircle. Y := 25;

ACircle. Radius := 17;

. . .

end;

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

Пример:

type

TColorCircle = class(TCircle);

BorderColor, InsideColor: TСolor;

constructor Create;

end;

var

AColorCircle: TColorCircle;

begin

. . .

AColorCircle := TColorCircle. Create;

. . .

AColorCircle. BorderColor := clBlack;

AColorCircle. InsideColor := clYellow;

. . .

AColorCircle. X :=10;

AColorCircle. Y := 25;

AColorCircle. Radius := 17;

. . .

end;

Как иллюстрируют последние три команды, Вы можете ссылаться на поля, объявленные внутри класса TСircle, поскольку они также являются частью экземпляра TColorCircle. Эти поля наследуются от TСircle, и Вы можете указывать их, как если бы они объявлялись внутри TColorCircle.

Экземпляр объекта надо рассматривать как единый, неделимый, структурированный набор всех унаследованных полей и методов вместе с полями и методами, объявленными в данном классе. Эффективность наследования состоит в том, что надо указывать не все поля в экземпляре, а только те, которые являются новыми на данном уровне наследования.

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

Для доступа к полям объекта можно использовать оператор with, как и в обычных записях.

Пример:

with AColorCircle do

begin

X:=10;

Y:=25;

Radius:=17;

end;

1.12 Методы

Метод - это подпрограмма, которая определена как часть класса и включена внутрь этого класса.

Всего существует шесть разновидностей методов объектов:

1) Методы - процедуры. Они аналогичны самостоятельным процедурам, за исключением того, что они “присоединены” к тому классу, в котором заданы, и могут быть вызваны лишь через какого-либо действительного представителя этого класса.

2) Методы - функции. Они возвращают значения и ведут себя так же, как и обычные самостоятельные функции. Но разница в том, что они “присоединены” к тому классу, в котором заданы, и должны вызываться через действительного представителя этого класса.

3) Классовые процедуры. Они концептуально даже более близки к обычным самостоятельным процедурам, чем методы - процедуры. Для вызова классовых процедур не требуется экземпляр объекта. Эти процедуры объявляются как часть класса и вызываются с использованием ссылки на сам класс, а не на представителя этого класса.

4) Классовые функции. Они аналогичны классовым процедурам. При их использовании не требуется представителя класса. Эти функции вызываются посредством ссылки на сам класс.

5) Конструкторы. Это специальные методы, ведущие себя аналогично классовым функциям. Они вызываются с помощью ссылки на класс, в котором заданы, Возвращаемое конструктором класса значение является ссылкой на вновь созданного представителя этого класса. Таким образом и создаются экземпляры объектов - путём вызова конструктора.

6) Деструкторы. Они также являются специальными методами объектов. Они похожи на методы-процедуры и вызываются аналогично. Деструкторы отвечают за уничтожение экземпляра объекта. Деструкторы - это методы объектов. Для их вызова требуется использовать представителя класса.

1.12.1 Методы - функции и методы - процедуры

Методы-процедуры и методы-функции объявляются так же, как и обычные процедуры и функции. Разница в том, что это делается не в блоке объявлений языка Pascal, а в описании класса.

Общий синтаксис объявления метода - процедуры:

type

ИмяКласса = class (Имя Родительского Класса)

. . .

procedure ИмяМетода (<Список параметров>);

. . .

end;

Пример:

type

TDateList = class (TPersistent)

. . .

procedure EndUpDate;

procedure LoadFromFile (const FileName: String);

procedure SaveToFile (const FileName: String);

. . .

end;

Общий синтаксис объявления метода-функции выглядит так:

type

ИмяКласса = class (ИмяРодителя)

. . .

function ИмяМетода (<Список параметров>): ВозвращаемыйТип;

. . .

end;

Примеры:

type

TClientWindow = class(TWindow)

. . .

function Hide;

function GetRect: Trect;

function GetProperty(const Aname: String): THandle;

. . .

end;

Объявления элементов класса могут быть сгруппированы в разделы например, public, private. В пределах каждого раздела все поля должны появляться до объявления методов.

1.12.2 Конструкторы

Конструкторы объявляются почти так же, как и другие методы, только вместо слов procedure или function ставится зарезервированное слово constructor.

Конструктор - специальный вид подпрограммы, присоединенной к классу. Его роль - создавать представителей класса. Он ведет себя как функция, которая возвращает ссылку на вновь созданный экземпляр объекта.

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

type

ИмяКласса = class(ИмяРодителя)

. . .

constructor Имя(<Список параметров>);

. . .

end;

Примеры:

type

TClientWindow = class(TWindow)

. . .

constructor Create(AnOwner: TWindow);

constructor CreateOffDesktor;

. . .

end;

В классе может определяться несколько конструкторов. Обычно у класса бывает только один конструктор. Общепринятое имя для единственного конструктора - Сreate.

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

1.12.3 Деструкторы

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

Синтаксис объявления деструктора :

type

ИмяКласса = class(ИмяРодителя)

. . .

destructor Имя(<Список параметров >);

. . .

end;

Примеры:

type

TClientWindow = class(TWindow)

. . .

destructor Destroy;

destructor DestroyAndNotify(Receipient: TManager);

. . .

end;

В одном классе может объявляться несколько деструкторов. Обычно в классе имеется только один деструктор без параметров с именем Destroy.

В качестве примера объявления конструкторов и деструкторов рассмотрим полное описание класса TBook.

type

TBook=class

FAuthor : TAuthor;

FTitle : String;

constructor Create;

destructor Destroy; override;

function GetAuthor : TAuthor;

procedure SetAuthor (const AnAuthor : TAuthor);

function GetTitle : String;

procedure SetTitle (ATitle : String);

end;

Теперь в классе TBook объявляются конструктор Create и деструктор Destroy, которые могут использоваться соответственно для создания и уничтожения представителей этого класса.

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

1.12.4 Классовые процедуры и функции

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

Классовые методы объявляются точно так же, как и обычные методы, но только перед их объявлением ставится зарезервированное слово class.

Синтаксис объявления классовой процедуры:

type

ИмяКласса = class (ИмяРодителя)

. . .

class procedure ИмяМетода(<Список параметров>);

. . .

end;

Рассмотрим пример:

type

TDataList = class(TPersistent)

. . .

class procedure FreeAll;

class procedure SetDelimiter(ASymbol : Char);

. . .

end;

Объявление классовой функции - аналогично.

1.12.5 Реализация методов

После успешного объявления метода в определении класса надо реализовать его, то есть дать описание действий, которые должны быть выполнены представителем класса при вызове метода.

Между самостоятельными подпрограммами и методами существует различие в способе доступа к данным представителя класса.

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

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

Хотя этого дополнительного параметра, который называется self, не видно в определении метода, он в определении неявно присутствует. Параметр Self имеет тот же тип, что и класс, в котором определен метод.

Все тело метода заключено в неявный оператор with Self do, который предоставляет методу доступ к полям, свойствам и другим методам данного представителя класса.

Неявный оператор with выглядит примерно так:

begin

with Self do begin

. . .

{Код метода}

. . .

end;

end;

Можно напрямую ссылаться на поля объекта, как если бы это были локальные переменные.

Иногда возникают ситуации, когда приходится явно использовать параметр Self. Например, этот параметр может понадобиться для разрешения конфликтов идентификаторов внутри метода.

Чтобы одновременно работать и с локальной переменной, и с полем объекта, имена которых совпадают, перед именем поля объекта надо поставить префикс Self, например, Self. FTitle.

Большая разница между обычными и классовыми методами состоит в том, что у последних отсутствует Self. Так как классовые методы можно вызвать, как используя экземпляр класса, так и ссылаясь непосредственно на сам класс, то классовый метод не может иметь доступа к полям представителя класса.

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

Обычные методы-процедуры реализуются таким же способом, что и самостоятельные процедуры в соответствии с синтаксисом:

procedure ИмяКласса. ИмяПроцедуры (<Параметры>);

<Необязательный блок объявлений>

begin

<Исполняемые операторы>

end;

Внутри метода имеется доступ не только к локальным и любым видимым внешним идентификаторам, но и к полям экземпляра объекта, инкапсулированным в определении класса.

Например, метод-процедура SetTitle определенного ранее класса TBook могла бы выглядеть так:

procedure TBook. SetTitle (ATitle : String);

begin

FTitle := ATitle;

end;

Переменная FTitle не определяется явным образом, как локальная переменная, но эквивалентна

Self. FTitle

то есть является полем активного (использованного при вызове) представителя класса Tbook.

Для реализации метода-функции GetTitle из § 12.3 ограничимся возможным примером:

function TBook. GetTitle : String;

begin

Result := FTitle;

end;

Здесь метод-функция GetTitle возвращает значение, хранящееся в поле FTitle активного объекта.

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

Рассмотрим пример:

1: constructor TBook. Create (ATitle : String);

2: begin

3: inherited Create;

4: FTitle ;= ATitle;

5: FAuthor := Nil;

6: end;

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

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

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

Типичная структура конструктора:

constructor Класс. ИмяМетода (<Параметры>);

<Блок локальных объявлений>

begin

inherited ИмяМетода (<Параметры>);

<Инициализация специфических полей>

end;

Пример:

constructor TBook. Create(AuAuthor: TAuthor; ATitle: String);

begin

inherited Create;

FAuthor := AuAuthor;

FTitle := ATitle;

end;

Как правило, вызывать подходящий наследуемый конструктор нужно в первом же выполняемом операторе блока begin - end конструктора.

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

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

Общий синтаксис реализации деструктора следующий:

destructor ИмяКласса. ИмяДеструктора (<Параметры>);

<Необязательный блок объявлений>

begin

<Исполняемые операторы>

end;

Рассмотрим пример:

destructor TBook. Destroy;

begin

FTitle:=‘ ‘.

inherited Destroy;

end;

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

Разница между встроенным (прямым) и ссылочным (косвенным) полем заключается в том, что встроенные поля непосредственно содержат значения атрибута данных, в то время как ссылочные поля содержат ссылку на какой-нибудь другой объект. Значения встроенным полям присваиваются непосредственно.

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

Пример:

program Undef1;

type

Tcustomer = class

. . .

constructor Create;

destructor Destroy; override;

. . .

end;

var Acustomer; Tcustomer;

begin

1

12 {экземпляр Acustomer ещё не создан, его нельзя использовать}

1

14 Acustomer :=TCustomer. Create; {конструктор}

1

16 {экземпляр Acustomer создан, его можно использовать}

1

18 ACustomer. Destroy; {деструктор}

1

20 {экземпляр Acustomer уничтожен, его больше нельзя использовать}

2

end.

Хотя переменная Acustomer видима и доступна с 11 по 13 строки, она не может использоваться, поскольку её ещё не инициализировали.

Строки с 15 по 17 - это раздел кода, в котором переменная Acustomer содержит действительный экземпляр класса Tcustomer. В этом месте можно работать с полями иметодами объекта Acustomer.

После того, как экземпляр объекта в строке 18 уничтожается, переменная Acustomer перестаёт быть действительной. В строках с 19 по 21 уже нельзя обращаться к её полям и методам, точно так же, как этого нельзя было делать в строках 11-13, то есть до того, как экземпляр был создан. В обоих случаях экземпляр объекта, на который ссылается переменная Acustomer, не существует.

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

Типичная структура деструктора:

destructor Класс. ИмяДеструктора (<Параметры>);

<Блок локальных объявлений>

begin

<Уничтожение собственных полей>

inherited ИмяДеструктора (<Параметры>);

end;

Приведем пример:

destructor TBook. Destroy;

begin

FAuthor. Destroy;

FTitle. Destroy;

inherited Destroy;

end;

Если для класса был создан конструктор, то скорее всего в этом классе потребуется и деструктор. В частности, если конструктор резервирует какие-то ресурсы или в процессе работы создаёт другие экземпляры объектов, то обычно деструктор должен освободить ресурсы и уничтожить созданные экземпляры. Есть два исключения из этого правила:

1) Если внутри компонента создаётся другой, подчинённый, компонент и указывается, что текущий экземпляр объекта является его владельцем, то подчинённый компонент при необходимости уничтожается автоматически.

2) Ситуация, когда у класса нет никаких косвенных полей данных. В случае, когда объект не создаёт и не включает в себя никаких подчинённых объектов, деструктор может оказаться ненужным. В такой ситуации, даже если в определении класса были добавлены новые поля, класс не отвечает за их уничтожение.

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

Другими словами, с реализацией классового метода надо обращаться так, как будто пишется самостоятельная подпрограмма.

Общий синтаксис реализации классовой процедуры:

class procedure ИмяКласса. ИмяПроцедуры (<Параметры>);

<Необязательный блок объявлений>

begin

<Выполняемые операторы>

end;

Приведем пример:

class procedure TParser. SetDelimeter (const ASymbol: String);

begin

ParserDelimeter:=ASymbol;

end;

1.12.6 Вызов методов

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

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

Общий синтаксис вызова конструктора следующий:

var

AnInstance:ИмяКласса;

begin

. . .

AnInstance:=ИмяКласса. ИмяКонструктора(<Параметры>);

. . .

end;

Например:

var

FormInstance:TForm1;

begin

FormInstance:=TForm1.Create(Self);

end;

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

Синтаксис для вызова, или активации, метода следующий:

ИмяЭкземпляра. ИмяМетода(<Параметры>);

ИмяМетода - это действительная ссылочная переменная, то есть, переменная, которая была предварительно инициализирована посредством вызова конструктора.

Примеры:

Form1.Create;

Button1.Free;

Имя метода вместе с префиксом и разделительной точкой называется полностью квалифицированным именем метода Вместо полностью квалифицированных имен можно воспользоваться оператором with для получения прямого доступа к методам. Это аналогично использованию оператора with для доступа к полям.

Синтаксис для вызова деструктора:

ИмяЭкземпляра. ИмяДеструктора(<Параметры>);

Пример:

Button1.Destroy;

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

Поэтому рекомендуется явно присваивать переменной значение Nil сразу же после того, как ее экземпляр объекта уничтожен. Тогда впоследствии можно определить, что она не содержит действительного экземпляра объекта и, если понадобится, вновь его создать.

Простой вызов деструктора не установит переменную в Nil. Надо явно присвоить значения всем переменным, которые ссылаются на уничтоженный экземпляр.

Методы для доступа к полям. Хотя с помощью оператора with можно получить прямой доступ к полям объекта, использование такого подхода не рекомендуется.

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

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

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

1) Надежность данных. Предотвращаются некорректные изменения элемента данных, благодаря дополнительной проверке в методе значения на допустимость. Тем самым гарантируется, что экземпляр объекта будет всегда находиться в “хорошем” состоянии.

2) Целостность ссылок. Перед доступом к объекту можно удостовериться, что косвенное поле содержит корректное значение. Если обнаружится некорректное значение (например, если экземпляр объекта, на который указывает ссылка, больше не существует), то можно инициировать действия по исправлению положения, например, заново создать связанный экземпляр объекта.

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

4) Сокрытие информации. Когда доступ к данным осуществляется только через методы, то можно скрыть детали реализации объекта. Позднее, если реализация изменится, достаточно изменить лишь реализацию методов доступа к полям. Те же части программы, которые использовали класс, не будут затронуты.

Классовые методы могут быть вызваны двумя способами:

1) Через представителей класса, как и обычные методы.

2) С использованием непосредственного имени класса. При этом не требуется ссылаться на какого-либо представителя этого класса.

Для вызова классовой процедуры используется синтаксис:

begin

. . .

ИмяЭкземпляра. ИмяКлассовогоМетода(<Параметры>);

. . .

end;

или

begin

. . .

ИмяКласса. ИмяКлассовогоМетода(<Параметры>);

. . .

end;

Пример:

type

TDataBase=class

class procedure SetDateDelimiter(AChar: Char);

end;

. . .

class procedure TDataBase. SetDateDelimiter;

begin

. . .

end;

. . .

var

TheDataBase: TDataBase;

begin

. . .

TheDataBase. SetDateDelimiter(‘/’);

TDataBase. SetDateDelimiter(‘-’);

. . .

end;

Аналогичным образом вызываются классовые функции.

1.13 Наследование и полиморфизм

Основа популярности ООП - в использовании классов, наследовании и полиморфизме. Ранее мы познакомились с классами, которые включают в себя поля и методы для работы с этими полями. наследование лаёт возможность разработчикам программного обеспечения расширять классы, определяя классы-потомки, которые получают по наследству поля и методы родительских классов.

1.13.1 Наследование

Наследование позволяет создать новый производный класс, или класс-потомок, из уже существующего. Можно повторять создание производных классов, формируя иерархию классов. Первый класс - предшественник в иерархии называется базовым классом. Классы, из которых происходит класс - потомок, называются классами - предшественниками. Класс - непосредственный предшественник называется родительским классом. Важность наследования в том, что классу - потомку не приходится “изобретать велосипед”, объявляя заново все поля данных и методы. Ему нужно объявлять лишь новые поля. Класс определяет не только новые методы, но и те, которые должны заместить унаследованные от предшественников. Замещение наследуемых методов нередко оказывается необходимым для подстройки той или иной операции в соответствии с нуждами класса - потомка.

1.13.2 Полиморфизм

Полиморфизм – третья и самая мощная грань объектно-ориентированного программирования (первые две – это инкапсуляция и наследование) [1]. Полиморфизм может быть описан как поведенческая абстракция – возможность вызывать действие или поведение по имени какого-либо конкретного экземпляра объекта, не зная в точности, какая именно реализация метода при этом будет вызвана, и даже не зная типа, к которому принадлежит данный объект. Тип представителя класса и реализация метода, который будет вызван, не могут быть полностью определены на фазе трансляции, но будут полностью определены во время выполнения программы. Более того, процесс определения будет выполняться динамически – один и тот же машинный код может вызвать метод method1 производного класса descendantclass1 на одном проходе, и method1 производного класса descendantclass2 на другом проходе, если при разных проходах используются различные экземпляры объектов.

Полиморфизм возможен только тогда, когда есть:

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

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

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

Например, можно разработать иерархию классов простых графических форм (точек, линий, прямоугольников, эллипсов, окружностей и т. п.),сделав все эти классы производными от базового класса TShape, в котором будут определены методы, общие для всех его наследников. Одним из таких общих методов может быть виртуальный метод Draw. Каждый конкретный наследник класса TShape будет переопределять его, реализуя в нём прорисовку своей собственной фигуры. Если потребуется хранить список таких фигур, то можно создать массив с базовым классом TShape, и присвоить элементам этого массива представителей конкретных производных классов – TCircle, TLine, TSquare и т. д.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5