Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
1_SAOD_-_Dinamicheskie_peremennye_OOP.doc
Скачиваний:
12
Добавлен:
21.03.2016
Размер:
1.06 Mб
Скачать

Тема 3. Объектно-ориентированное программирование

  1. Основные концепции ООП

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

К основным концепциям ООП относятся следующие: инкапсуляция, наследование, полиморфизм.

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

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

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

Пример объявления класса-потомка:

tAnyClass=class(tParentClass)

// Добавление к классу tParentClass новых

// и переопределение существующих элементов

end;

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

Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нём нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноимённых метода (возможно с разным набором параметров), имеющие разную реализацию и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.

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

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

Классом вDelphiназывается особая структура, состоящая из полей, методов и свойств. Такой тип также будем называтьобъектнымтипом.

type

tMyClass = class (tObject) // класс tMyClass

fMyField: Integer; // поле fMyField

function MyMethod(Data:tData): Integer; // метод MyMethod

end;

Поскольку класс – это тип, то принято перед собственно именем записывать префикс t (от словаtype– тип). Переменная типа класс называется экземпляром класса, или объектом:

var AMyObject: tMyClass; // объект класса tMyClass

Поля класса – это данные, уникальные для каждого созданного в программе экземпляра класса. Они могут иметь любой тип, в том числе – тип класс. ВDelphiперед именами полей принято ставить символf(отfield– поле):fLength,fWidth,fMyFileldи т. п.

Методы – это процедуры и функции, описанные своими заголовками внутри класса и предназначенные для операций над его полями. В отличие от полей, методы у всех объектов одного класса общие. От обычных процедур и функций методы отличаются тем, что им при вызове передается (неявно) указатель на тот объект, который их вызвал. Поэтому обрабатываться будут поля именно того объекта, который вызвал метод. Внутри метода указатель на вызвавший его объект доступен под зарезервированным именемSelf.

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

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

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

function tMyClass.MyMethod(Data:tData): Integer; // заголовок метода tMyClass.MyMethod

begin

// тело метода tMyClass.MyMethod

end;

Разрешено опережающее объявление классов единственным словомclassс последующим полным описанием:

type

tFirstClass = class;

tSecondClass = class(tObject)

f1 : tFirstClass;

end;

tFirstClass = class(tObject)

f2 : tSecondClass;

end;

  1. Создание и уничтожение объектов

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

Для выделения памяти экземпляру любой динамической переменной используется процедура New, для уничтожения – процедураDispose. С объектами эти процедуры не используются: объект создается специальным методом – конструктором, а уничтожается–деструктором:

AMyObject:=tMyClass.Create;// создание объекта AMyObject класса tMyClass

// действия с созданным объектом

AMyObject.Destroy;// уничтожение объекта AMyObject

Конструктор– это специальный метод, заголовок которого начинается зарезервированным словомconstructor. Функция конструктора заключается в выделении памяти под экземпляр класса (объект) и установлении связи между созданным объектом и специальной информацией о классе. В теле конструктора можно расположить любые операторы, которые необходимо выполнить при создании объекта, например, присвоить полям начальные значения. ВDelphiконструкторов у класса может быть несколько. Общепринято называть конструкторCreate.

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

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

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

constructor tMyClass.Create;

begin

inherited Create;

...

end;

Деструктор– это специальный виртуальный (см. ниже) метод, заголовок которого начинается зарезервированным словомdestructor. Деструктор предназначен для удаления объектов.Типичное название деструктора – Destroy. Пример описания класса с конструктором и деструктором:

type

tMyClass = class (tObject) // класс tMyClass

fMyField: Integer; // поле fMyField

constructor Create; // конструктор Create

destructor Destroy; override; // деструктор Destroy

function MyMethod(Data:tData): Integer; // метод MyMethod

end;

Для уничтожения объекта рекомендуется использовать метод Free(он есть у базового классаtObject), который первоначально проверяет указатель на объект (не равен ли онnil) и только затем вызывает деструкторDestroy:

AMyObject.Free;

Деструктор очень часто не переопределяется в классе-потомке.

  1. Инкапсуляция. Свойства

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

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

type

tMyClass = class (tObject)

function GetAProperty: tSomeType;

procedure SetAProperty(ANewValue: tSomeType);

property AProperty: tSomeType read GetAProperty write SetAProperty;

end;

В контексте свойства слова readиwriteявляются зарезервированными. Для доступа к значению свойстваAPropertyявно достаточно написать

AMyObject.AProperty := AValue;

AVariable := AMyObject.AProperty;

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

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

tPropClass = class(tObject)

fValue: tSomeType;

procedure DoSomething;

function Correct(AValue: Integer):Boolean;

procedure SetValue(NewValue: Integer);

property AValue: Integer read fValue write SetValue;

end;

procedure tPropClass.SetValue(NewValue: Integer);

begin

if (NewValue<>fValue) and Correct (NewValue) then fValue := NewValue;

DoSomething;

end;

В этом примере чтение значения свойства AValueозначает просто чтение поляfValue. Зато при присвоении ему значения внутриSetValueвызывается сразу два метода.

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

tMyClass = class (tObject)

property AProperty: tSomeType read GetValue;

end;

В этом примере вне объекта значение свойства можно лишь прочитать; попытки присвоить APropertyзначение вызовет ошибку компиляции.

Для присвоения свойству значения по умолчанию используется ключевое слово default:

property Visible: Boolean read fVisible write SetVisible default True

Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:

property APoints[Index: Integer]:tPoint read GetPoint write SetPoint;

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

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

Для векторного свойства необходимо описать не только тип элементов массива, но также имя и тип индекса. После ключевых слов readиwriteв этом случае должны стоять имена методов –использование здесь имени поля типа массив недопустимо. Метод чтения значения векторного свойства должен быть описан как функция, возвращающая значение того же типа, что и элементы свойства, и имеющая единственный параметр: того же типа и с тем же именем, что и индекс свойства:

function GetPoint(Index: Integer): TPoint;

Метод записи значения в такое свойство должен первым параметром иметь индекс, а вторым –переменную нужного типа (которая может быть передана как по ссылке, так и по значению):

procedure SetPoint(Index: Integer; NewPoint: tPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi(списки, наборы строк) «построены» вокруг векторного свойства. Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано какdefault:

tМуClass = class

property Strings[Index: Integer]: string read Get write Put; default;

end;

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

var AMyObject: tMyClass;

begin

AMyObject.Strings[1] := 'First'; // первый способ

AMyObject[2] := 'Second';// второй способ

...

end;

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

О роли свойств в Delphiкрасноречиво говорит следующий факт: у всех имеющихся в вашем распоряжении стандартных классов 100%полей недоступны (помещены в секциюprivate) и заменены базирующимися на них свойствами. Рекомендуем при разработке своих классов придерживаться этого же правила.

  1. Наследование. Методы

Принцип наследования позволяет объявить класс

tNewObject =class(tOldObject);

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

В Delphiвсе классы являются потомками классаtObject. При построении дочернего класс прямо отtObjectв определении его можно не упоминать. Следующие объявления одинаково верны:

tMyObject = class(tObject);

tMyObject = class;

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

Унаследованные от предка поля и методы доступны в дочернем классе; если имеет место совпадение имен методов, то говорят, что они перекрываются.

По тому, какие действия происходят при вызове, методы делятся на три группы: 1) статические, 2) виртуальные (virtual) и динамические (dynamic), 3) перегружаемые (overload) методы.

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

Принципиально отличаются от статических виртуальные и динамические методы (директиваvirtualилиdynamic). Их адрес определяется во время выполнения программы по специальной таблице. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе толькоодноименными методами, имеющимитот же тип.

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

  1. Полиморфизм. Виртуальные и динамические методы

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

tField = class

function GetData: string; virtual; abstract;

end;

tStringField = class(tField)

fData : string;

function GetData: string; override;

end;

tIntegerField = class(tField)

fData : Integer;

function GetData: string; override;

end;

tExtendedField = class(tField)

fData : Extended;

function GetData: string; override;

end;

function tStringField.GetData;

begin

Result := fData;

end;

function tIntegerField.GetData;

begin

Result := IntToStr(fData);

end;

function tExtendedField.GetData;

begin

Result:=FloatToStr(fData);

end;

procedure ShowData(AField:tField);

begin

Writeln(AField.GetData);

end;

В этом примере классы содержат разнотипные данные и «умеют» сообщать о значении этих данных текстовой строкой (при помощи метода GetData). Внешняя по отношению к ним процедураShowDataполучает объект в виде параметра и показывает эту строку.

Правила контроля соответствия типов (typecasting) языкаDelphiгласят, чтообъекту как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. В процедуреShowDataпараметр описан какtField –это значит, что в нее можно передавать объекты классов и tStringField,иtIntegerField, иtExtendedField, и любого другого потомкаtField.

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

При вызове виртуальных и динамических методов адрес определяется не во время компиляции, а во время выполнения – это называется поздним связыванием (late binding).Позднее связывание реализуется с помощью таблицы виртуальных методов (Virtual Method Table, VMT) и таблицы динамических методов (Dynamic Method Table, DMT).

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

Динамические методы вызываются медленнее, но позволяют более экономно расходовать память. Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице; в случае неудачи просматриваются таблицы DMTвсех классов-предков в порядке иерархии и, наконец,DMTклассаtObject, где имеется стандартный обработчик вызова динамических методов. Экономия памяти налицо.

Для перекрытия и виртуальных, и динамических методов служит директива override, с помощью которой (и только с ней!) можно переопределять оба этих типа методов. Приведем пример:

tClass1 = class

fField1: Integer;

fField2: Longint;

procedure stMet;

procedure vrMet1; virtual;

procedure vrMet2; virtual;

procedure dnMet1; dynamic;

procedure dnMet2; dynamic;

end;

tClass2 = class(tClass1)

procedure stMet;

procedure vrMet1; override;

procedure dnMet1; override;

end;

var

Obj1: tClass1;

Obj2: tClass2;

Первый метод класса tClass2 создается заново, остальные два –перекрываются. Попытка применить директивуoverrideк статическому методу вызовет ошибку компиляции.

  1. Пример реализации полиморфизма для иерархии графических объектов

Рассмотрим описание и реализацию классов «Точка» и «Окружность» для «рисования» на экране точки и окружности.

type

tCoord = Word; // тип – координата точки на экране

// Описание класса tPoint

tPoint = class(tObject) // класс – точка на экране дисплея

protected

fX, fY: tCoord; // поля – координаты точки на экране

fColor: Byte; // поле – цвет рисования точки

public

property X: tCoord read fX write fX; // свойствокоордината X

property Y: tCoord read fY write fY; // свойствокоордината Y

property Color: Byte read fColor write fColor; // свойствоцвет

procedure Show; // метод высвечивания точки

procedure Hide; // метод гашения точки

procedure MoveTo(NewX, NewY: tCoord); // метод перемещения точки

end;

// Описание класса tCircle

tCircle = class(tPoint) // класс – окружность на экране

protected

fRadius: tCoord; // полерадиус окружности

public

property Radius: tCoord read fRadius write fRadius; // свойстворадиус

procedure Show; // метод высвечивания окружности

procedure Hide; // метод гашения окружности

procedure MoveTo (NewX,NewY:tCoord) // метод перемещения окружности

end;

// Реализация методов класса tPoint

procedure tPoint.Show;

begin

Writeln('Рисую точку (', fx, ', ', fy, ') цветом ', fColor); // «рисование» точки

end;

procedure tPoint.Hide;

begin

Writeln('Гашу точку (', fx, ', ',fy, ') цвета ', fColor); // «гашение» точки

end;

procedure tPoint.MoveTo(NewX, NewY: tCoord);

begin

Hide;

fX := NewX; fY := NewY;

Show;

end;

// Реализация методов класса tCircle

procedure tCircle.Show;

begin // «рисование» окружности

Writeln('Рисую окружность с центром (',fx,',',fy,') радиуса ',fRadius,' цветом ',fColor);

end;

procedure tCircle.Hide; // «гашение» окружности

begin

Writeln('Гашу окружность с центром (', fx, ', ', fy, ') радиуса ', fRadius, ' цвета ', fColor);

end;

procedure tCircle.MoveTo (NewX,NewY: tCoord);

begin

Hide;

fX := NewX; fY := NewY;

Show;

end;

Обратите внимание, что методы MoveTo классов tPoint и tCircle реализуются одинаково за исключением того, что в tPoint.MoveTo используются tPoint.Hide и tPoint.Show, а в tCircle.MoveTo используются tCircle.Hide и tCircle.Show. Взаимодействие методов классов tPoint и tCircle можно проиллюстрировать следующей схемой:

Поскольку методы реализуются одинаково, можно попытаться унаследовать метод MoveToот классаtPoint. При этом возникает следующая ситуация. При компиляции методаtPoint.MoveToв него будут включены ссылки на коды методовtPoint.ShowиtPoint.Hide. Так как метод с именемMoveToне определен в классеtCircle, то компилятор обращается к родительскому типу и ищет в нем метод с этим именем. Если метод найден, то адрес родительского метода заменяет имя в исходном коде потомка. Следовательно, при вызовеtCircle.MoveToв программу будут включены кодыtPoint.MoveTo(то есть классtCircleбудет использовать метод так, как он откомпилирован). В этом случае процедураtCircle.MoveToбудет работать неверно.

Структура вызовов методов при этом будет следующей:

В нашем примере процедуры ShowиHideдолжны быть объявлены виртуальными, так как только в этом случае классtCircleможет наследовать методMoveToу типаtPoint. Это становится возможным потому, что подключение виртуальных методовShowиHideк процедуреMoveToбудет осуществляться во время выполнения программы, и будут подключаться методы, определенные для типаtCircle(tCircle.ShowиtCircle.Hide). Напомним, что подключение виртуальных методов осуществляется с помощью специальной таблицы виртуальных методов (VMT).

При использовании виртуальных методов ShowиHideвзаимодействие методов классовtPointиtCircleможно проиллюстрировать следующей схемой:

Описание классов «Точка» и «Окружность» с использованием виртуальных методов:

// Описание класса tPoint

tPoint = class(TObject)

protected

fX, fY: tCoord;

fColor: Byte;

public

property X: tCoord read fX write fX;

property Y: tCoord read fY write fY;

property Color: Byte read fColor write fColor;

procedure Show; virtual;

procedure Hide; virtual;

procedure MoveTo(NewX, NewY: tCoord);

end;

// Описание класса tCircle

tCircle = class (tPoint)

protected

fRadius: tCoord;

public

property Radius: tCoord read fRadius write fRadius;

procedure Show; override;

procedure Hide; override;

end;

  1. Перегрузка методов

В последних версиях Delphiпоявилась новая разновидность методов –перегружаемыеметоды. Эту категорию методов нельзя назвать антагонистом двух предыдущих: и статические и динамические методы могут быть перегружаемыми. Перегрузка нужна, чтобы выполнить одинаковые или похожие действия с разнотипными данными. Рассмотрим пример:

type

tClass1 = class

i : Extended;

procedure SetData(AValue : Extended);

end;

tClass2 = class(tClass1)

j : Integer;

procedure SetData(AValue : Integer);

end;

var

Obj1: tClass1;

Obj2: tClass2;

Попытка вызова методов

Obj2.SetData(1.0);

Obj2.SetData(1);

вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри Obj2статический метод с параметром типаExtendedперекрыт, и он его не «признает». Объявить методы виртуальными нельзя, так как тип и количество параметров в одноименных виртуальных методах должны совпадать. Чтобы указанные вызовы были верными, необходимо объявить методы перегружаемыми, для чего используется директиваoverload:

type

tClass1 = class

i : Extended;

procedure SetData(AValue : Extended); overload;

end;

tClass2 = class(tClass1)

j : Integer;

procedure SetData(AValue : Integer); overload;

end;

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

Можно перегружать и виртуальные методы. В этом случае необходимо добавить директиву reintroduce:

type

tClass1 = class

i : Extended;

procedure SetData(AValue : Extended); overload; virtual;

end;

tClass2 = class(tClass1)

j : Integer;

procedure SetData(AValue : Integer); reintroduce; overload;

end;

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

  1. Абстрактные методы

Абстрактными называются методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в потомках класса. Абстрактными могут быть только виртуальные и динамические методы. В Delphiесть одноименная директива (abstract), указываемая при описании метода:

procedureNeverCallMe;virtual;abstract;

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

Пример с классом tFieldиз раздела 6 «Полиморфизм» поясняет, для чего нужно использование абстрактных методов. В данном случае классtFieldне используется сам по себе; его основное предназначение –быть родоначальником иерархии конкретных классов-«полей» и дать возможность абстрагироваться от частностей. Хотя параметр процедурыShowDataи описан какtField, но если передать в нее объект этого класса, произойдет исключительная ситуация вызова абстрактного метода.

  1. Области видимости

Области видимости – это возможности доступа к составным частям объекта. В Delphiполя и методы могут относиться к четырем группам: «общие» (public), «личные» (private), «защищенные» (protected) и «опубликованные» (published).

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

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

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

4. Область видимости, определяемая четвертой директивой – published, имеет особое значение для интерфейса визуального проектированияDelphi. В этой секции должны быть собраны те свойства объекта, которые будут видны не только во время исполнения приложения, но и из среды разработки. Все свойства компонентов, доступные через Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как иpublic.

Пример, иллюстрирующий первые три варианта областей видимости:

unit First;

interface

type

tClass1 = class

public

procedure Method1;

private

procedure Method2;

protected

procedure Method3;

end;

procedure TestProc1;

implementation

var

Obj1 : tClass1;

procedure TestProc1;

begin

Obj1:= tClass1.Create;

Obj1.Method1; // допустимо

Obj1.Method2; // допустимо

Obj1.Method3; // недопустимо

Obj1.Free;

end;

end.

unit Second;

interface

uses First;

type

tClass2 = class(tClass1)

procedure Method4;

end;

procedure TestProc2;

implementation

var Obj2 : tClass2;

procedure tClass2.Method4;

begin

Method1; // допустимо

Method2; // недопустимо

Method3;// допустимо

end;

procedure TestProc2;

begin

Obj2 : =tClass2.Create;

Obj2 .Method1; // допустимо

Obj2 .Method2; // недопустимо

Obj2 .Method3; // недопустимо

Obj2 .Free;

end;

end.

При описании дочернего класса можно переносить методы и свойства из одной сферы видимости в другую, не переписывая их заново и даже не описывая – достаточно указать новую сферу видимости наследуемого метода или свойства в описании дочернего класса. Разумеется, если вы поместили свойство в областьprivate, «достать» его оттуда в потомках возможности уже нет.

  1. Объект изнутри

Рассмотрим пример из раздела «Полиморфизм»:

tClass1 = class

fField1: Integer;

fField2: Longint;

procedure stMet;

procedure vrMet1; virtual;

procedure vrMet2; virtual;

procedure dnMet1; dynamic;

procedure dnMet2; dynamic;

end;

tClass2 = class(tClass1)

procedure stMet;

procedure vrMet1; override;

procedure dnMet1; override;

end;

var

Obj1: tClass1;

Obj2: tClass2;

Внутренняя структура объектовObj1иObj2имеет вид:

Указатель на объект Obj1

VMT класса tClass1

DMT класса tClass1

Указатель на классtClass1

RTTIкласса tClass1

Число динамических методов: 2

Поле fField1

@tClass1.vrMet1

Индекс tClass1.dnMet1 (-1)

Поле fField2

@tClass1.vrMet2

Индекс tClass1.dnMet2 (-2)

@tObject.Destroy

@tClass1.dnMet1

@tClass1.dnMet2

Указатель на объектObj2

VMT класса tClass2

DMT класса tClass2

Указатель на класс tClass2

RTTI класса tClass2

Число динамических методов: 1

Поле fField1

@tClass2.vrMet1

Индекс tClass2.dnMet1 (-1)

Поле fField2

@tClass1.vrMet2

@tClass2.dnMet1

@tObject.Destroy

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

Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат – в начале слово, содержащее количество элементов таблицы; затем – слова, соответствующие индексам методов. Нумерация индексов начинается с –1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Обратите внимание, что DMT объекта Obj1состоит из двух элементов,Obj2– из одного элемента, соответствующего перекрытому методуdnMet1. В случае вызова методаObj2.dnMet2индекс не будет найден в таблице DMTObj2, поиск продолжится в DMT объектаObj1. Именно так экономится память при использовании динамических методов.

Напомним, что указатель на класс указывает на адрес первого виртуального метода. Служебные данные размещаются перед таблицей VMT, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS.

В языке определены два оператора: isиas, неявно обращающиеся к информации о классе.

Оператор is предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Выражение вида:

AnObject is tObjectType

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

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

with ASomeObject as tAnotherType do ...

Использование оператора as отличается от стандартного способа приведения типов с помощью конструкции tAnotherType(ASomeObject)наличием проверки на совместимость типов во время выполнения (как в оператореis): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуацииEInvalidCast. После применения оператораasсам объект остается неизменным, но вызываются только те его методы, которые есть у присваиваемого класса. Присваиваемый фактически тип должен быть известен на стадии компиляции, поэтому на местеtObjectType(послеis) иtAnotherType(послеas) не может стоять переменная-указатель на класс.

Информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает вопрос: можно ли получить доступ к ней, не создавая экземпляр класса (объект)? Да, можно. Доступ к информациикласса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (classreference). Он описывается при помощи зарезервированных словclass of. Например, указатель на классtObjectописан в модуле SYSTEM.PAS и называетсяtClass:

type

tObject = class;

tClass = class of tObject;

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

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта –с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное словоclass:

type

tMyClass = class (tObject)

class function GetSize: string;

end;

var

MyObject: tMyClass;

AString: string;

begin

AString:=tMyClass.GetSize; // обращение к методу класса до создания объекта этого класса

MyObject:=tMyObject.Create;

AString:=MyObject.GetSize;

end.

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

Некоторые важные методы класса tObject

Метод

Описание

class function ClassName: ShortString;

Возвращает имя класса.

class function ClassParent: tClass;

Возвращает указатель на родительский класс (для tObject возвращает nil).

class function InstanceSize: Longint;

Возвращает размер экземпляра класса.

function ClassType: tClass;

Возвращает указатель на класс данного объекта.

constructor Create;

Конструктор. Создает новый экземпляр класса.

destructor Destroy; virtual;

Деструктор. Уничтожает экземпляр класса.

procedure Free;

Используется вместо деструктора. Проверяет передаваемый деструктору указатель на экземпляр.

В следующем примере переменная ObjectRef являетсяуказателем на класс; он по очереди указывает наtObjectиtMyObject(то есть на их внутренние структуры).Посредством этой переменной-указателя вызывается функция классаClassName:

type

tMyObject = class;

tMyObjClass = class of tObject;

var

ObjectRef : tMyObjClass;

s: String;

begin

ObjectRef := tObject;

s := ObjectRef .ClassName; // s :='TObject'

ObjectRef := tMyObject;

s:= ObjectRef .ClassName; // s : ='TMyObject'

end.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]