Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Ракитин Р.Ю. ООП в Turbo Delphi

.PDF
Скачиваний:
55
Добавлен:
18.03.2015
Размер:
3.59 Mб
Скачать

21

объекта обязательно присутствуют два метода: «создать объект» и «уничтожить объект». Во время создания объекта происходит выделение памяти для хранения необходимых свойств и заполняются значения по умолчанию. Во время уничтожения объекта происходит освобождение выделенной памяти.

Выделение памяти осуществляется при помощи специального метода класса конструктора, которому обычно присваивают имя Create. Для того чтобы подчеркнуть особую роль и поведение конструктора, в описании класса вместо слова procedure используется слово constructor.

+ В среде Delphi все компоненты являются наследниками базового

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

Удаление объекта осуществляется с помощью специального метода класса

деструктора Destroy. При этом имя данного метода записывается после слова destructor.

Ниже приведено описание класса TPerson, в состав которого введен конструктор и деструктор:

TPerson = class private

FName: string[15]; FAdres: string[35];

constructor Create; // конструктор destructor Destroy; // деструктор

public

procedure Show; // метод end;

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

var

<Название переменной>: <Название класса>

Такая переменная автоматически рассматривается как ссылка на объект, и

дополнительное указание компилятору на динамическую природу переменной не требуется.

Рассмотрим следующий пример, в котором объявляется два будущих объекта: Студент и Профессор:

var

Student: TPerson; Professor: TPerson;

22

Выделение памяти для данных объекта происходит путем присвоения результата применения метода-конструктора к типу (классу) объекта. Например, после выполнения инструкции

Professor := TPerson.Create;

выделяется необходимая память для данных объекта Professor.

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

constructor TPerson.Create; begin

FName := '';

FAdres := ''; end;

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

<Объект>.<Свойство>

или

<Объект>.<Метод>(<Список параметров>);

В нашем примере доступ к полю FName объекта Professor осуществляется через запись

Professor.FName.

Методы класса выполняют действия над объектами класса. Для обращения к методу Show, к объекту Professor в нашем примере можно записать:

Professor.Show;

Методы класса определяются в программе в разделе implementation точно так же, как и обычные процедуры и функции, за исключением того, что имя процедуры или функции, являющейся методом, состоит из двух частей: имени класса, к которому принадлежит метод, и имени метода. Имя класса от имени метода отделяется точкой.

Ниже приведен пример определения метода Show класса TPerson:

//метод Show класса TPerson procedure TPerson.Show; begin

ShowMessage('Имя:' + FName + #13#10 + 'Адрес:' + FAdres); end;

Как отмечалось ранее, если в программе какой-либо объект больше не используется, то необходимо освободить память, занимаемую полями данного

23

объекта. В нашем примере необходимо вызвать соответствующий деструктор класса:

Professor.Destroy;

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

Professor.Free;

3. Методы объектов

Методы это процедуры и функции, которые принадлежат объекту. Методы описываются внутри объявления объекта и бывают нескольких типов.

Static (статические) – это простые процедуры и функции. Если при описании метода вы ничего не указали, то используется именно этот тип. Для компилятора это самый простой тип метода, потому что в потомках такой метод не может быть изменен, и поэтому заранее можно узнать адрес этого метода в памяти.

Virtual (виртуальные) – такие методы могут быть переопределены в по- томках объекта. Например, если у вас есть объект «гараж» и метод «открыть ворота», то в его потомке этот метод может быть заменен улучшенной версией. Для определения адреса Delphi строит таблицу виртуальных методов, которая позволяет во время выполнения программы определить адрес метода. В такой таблице хранятся все методы текущего объекта и его предка.

Dynamic (динамические) – эти методы схожи с виртуальными, но для определения адреса используется другой способ. Для каждого объекта строится таблица только из его методов. Каждому методу назначается уникальный индекс. В данном случае экономится память, потому что не надо хранить адреса методов предков, но для поиска любого из них тратится намного больше времени.

Message (сообщения) – такие методы реагируют на события операционной системы. Для большинства сообщений ОС Windows в Delphi уже есть специальные обработчики событий, но если вам нужно, чтобы метод реагировал на определенное событие, которого нет у компонента, необходимо определить его вручную.

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

Если вы объявили метод virtual или dynamic, то можно переопределить его действия в наследнике/потомке.

24

4. Инкапсуляция и свойства объекта

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

В языке Object Pascal ограничение доступа к полям объекта реализуется при помощи свойств объекта. Свойство объекта характеризуется полем, сохраняющим значение свойства, и двумя методами, обеспечивающими доступ к полю свойства. Метод установки значения свойства называется методом записи свойства (write), а метод получения значения свойства методом чтения свойства (read). Описание простых свойств описывается следующим образом:

property <имя свойства>: <тип> read <метод чтения или имя поля>

write <метод записи или имя поля>;

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

Ниже приведен пример описания класса TPerson, содержащего два свойства: Name и Adres.

type

TName = string[15]; TAdres = string[35]; TPerson = class // класс private

FName: TName; // значение свойства Name FAdres: TAdres; // значение свойства Adres constructor Create(Name:TName);

procedure Show;

function GetName: TName; function GetAdres: TAddress;

procedure SetAdres(NewAdres: TAdres); public

//свойство Name доступно только для чтения property Name: TName read GetName;

//свойство Addres доступно для чтения и записи property Adres: TAdres

read GetAdres write SetAdres; end;

...

25

//реализация метода установки адреса procedure TPerson.SetAdres(NewAdres: TAdres); begin

FAdres:= NewAdres; end;

...

//реализация метода получения адреса function GetAdres: TAddress;

begin

GetAdres:= FAdres; end;

...

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

Student.Adres := 'С.Петербург, ул.Садовая 21, кв.3';

Компилятор перетранслирует приведенную инструкцию присваивания значения свойству в инструкцию вызова метода

Student.SetAdres('С.Петербург, ул.Садовая 21, кв.3');

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

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

procedure TPerson.SetAdres (NewAdres: TAdres); begin

if NewAdres<>’’ then FAdres:= NewAdres

else

ShowMessage(‘Введите не пустое значение адреса!’); end;

...

function GetAdres: TAddress; begin

if FAdres<>’’ then GetAdres:= FAdres

else

26

begin

GetAdres:=’nul’

ShowMessage(‘Адреса не существует!’); end;

end;

Оформление данных объекта как свойства позволяет ограничить доступ к полям, хранящим значения свойств объекта: например, можно разрешить только чтение. Для того чтобы инструкции программы не могли изменить значение свойства, в описании свойства надо указать лишь имя метода чтения. Попытка присвоить значение свойству, предназначенному только для чтения, вызывает ошибку. В приведенном выше описании класса TPerson свойство Name доступно только для чтения, а свойство Adres для чтения и записи.

5. Наследование

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

В объявлении класса-потомка указывается класс родителя. Например, класс TEmployee (Сотрудник) может быть порожден от рассмотренного выше класса TPerson путем добавления поля FDepartment (Отдел). Объявление класса TEmployee в этом случае может выглядеть так:

TEmployee = class(TPerson) FDepartment: integer; // номер отдела

constructor Create(Name:TName; Dep:integer); end;

Заключенное в скобки имя класса TPerson показывает, что класс TEmployee является производным от класса TPerson. В свою очередь, класс TPerson является базовым для класса TEmployee.

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

constructor TEmployee.Create(Name:TName; Dep:integer); begin

inherited Create(Name); FDepartment:= Dep;

end;

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

27

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

Engineer := TEmployee.Create('Сидоров',413); Engineer.Adres := 'ул.Блохина, д.8, кв.10';

Первая инструкция создает объект типа TEmployee, вторая устанавливает значение свойства, которое относится к родительскому классу.

6. Полиморфизм, виртуальные и абстрактные методы

Полиморфизм это возможность использовать одинаковые имена для методов, входящих в различные классы. Концепция полиморфизма

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

Определены три класса, один из которых является базовым для двух других:

tуре

// базовый класс

TPerson = class

FName: string; // имя

constructor Create(Name: string); function Info: string; virtual;

end;

// производный от TPerson TStud = class(TPerson)

FGr:integer; // номер учебной группы constructor Create(name:string; gr:integer); function Info: string; override;

end;

// производный от TPerson TProf = class(TPerson)

FDep:string; // название кафедры

constructor Create(name:string; dep:string); function Info: string; override;

end;

В каждом из этих классов определен метод Info. В базовом классе при помощи директивы virtual метод Info объявлен виртуальным. Объявление

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

Объявить список студентов и преподавателей можно так (здесь следует вспомнить, что объект это указатель):

28

List: array [l..SZL] of TPerson;

Объявить подобным образом список можно потому, что язык Object Pascal

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

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

Такие методы в классе описываются служебным словом abstract и обязательно переопределяются в потомках класса. Класс, в состав которого входят методы с отложенной реализацией, называется абстрактным. Создавать (конструировать) объекты виртуальных классов запрещается.

7. Определение принадлежности и приведение типов объектов.

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

Оператор is возвращает логическое значение (True объект принадлежит классу или False не принадлежит) и используется в следующем виде:

<имя объекта> is <имя класса>

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

...

interface

...

//заголовок процедуры

procedure MessageInfo(Person: TPerson);

...

implementation

...

//реализация процедуры

procedure MessageInfo(Person: TPerson); begin

{проверка соответствия объекта на который указывает переменная Person классу TProf}

if Person is TProf then

29

Person.Info //вызываем метод класса TProf else

Exit; //завершение процедуры end;

...

Операция as. Эта операция используется в тех случаях, когда тип объекта

может отличаться от типа исполняемой переменной для обеспечения доступа к «невидимым» полям и методам:

<имя объекта> as <имя класса>

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

Врассмотренном выше примере допускается вызов метода Info из

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

В результате использования оператора as Delphi пытается обращаться к объекту, на который указывает ссылка, как к экземпляру заданного класса. Так если бы в описании класса TPerson не было метода Info, а описание его располагалось бы в классе TProf, то метод можно было бы вызвать следующим образом:

...

if Person is TProf then (Person as TLine).Info

else

Exit;

...

+Приведение типов может использоваться в основном при работе с

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

8. Ключевое слово Self

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

30

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

В качестве примера можно привести метод некоторого класса, который выдает ссылку на экземпляр, из которого вызван данный метод во время выполнения программы:

tуре

AnyClass = class

function GetSelf: TObject; end;

...

function AnyClass.GetSelf: TObject; begin

{Возвращаем ссылку на экземпляр, из которого вызван метод}

Result:= Self end;

...

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

Классы и объекты Delphi

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