Основные механизмы и положения объектно - ориентированного программирования 



Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Основные механизмы и положения объектно - ориентированного программирования



Основные механизмы и положения объектно - ориентированного программирования

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

 

Инкапсуляция

 

5.1.1. Понятие класса и объекта

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

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

Отметим различие в терминологии Turbo Pascal и Delphi. В Pascal объектом называлась структура, получаемая в результате инкапсуляции. В Delphi такие структуры называются классами, а объекты — это экземпляры классов.

5.1.2. Структура класса

 

Описание класса. Свойства и методы

 

Описание классов разделено на две части — интерфейсную («заголовочную») и описательную. В интерфейсной части располагается заголовок класса, в котором указываются название класса, идентифицирующее его в программе, а также описания свойств и заголовков методов. В описательной части располагается программный код (реализация) методов, заголовки которых указаны в интерфейсной части описания класса.

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

Туре

…………………….

<Имя класса> = class   {Заголовок описания}

<Имя свойства 1>: <Тип свойства 1>;    {Описание свойства 1]

<Имя свойства N>: <Тип свойства N>;    {Описание свойства N}

 <Заголовок метода 1>; {Описание метода 1}

………………………

<Заголовок метода М>; {Описание метода М}

End;

 

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

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

или для методов-функций:

Function  <Имя класса>. <Имя метода> (<Список параметров>): <Тип значения>;

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

В листинге 5.1 приведен пример описания модуля, содержащего описание класса.

 

Листинг 5.1. Описания модуля, содержащего описание класса

Unit UsingClasses;

Interface

Type

T с ar = class                   {Заголовок класса содержит его название Tсar}

Mark: AnsiString;

  {Описание текстового свойства Mark для хранения марки автомобиля }

EngineVolume: Double; {Описание вещественного свойства

 для хранения объема двигателя}

Fuel: Double;      {Описание вещественного свойства

для хранения объема топлива}

Function StartEngine: Boolean; {Заголовок функции-метода

"Запустить двигатель"}

Procedure StopEngine;     {Заголовок процедуры-метода

    "Остановить двигатель"}

Procedure AddFuel(NewFuel: Double);

{Заголовок процедуры-метода

"Заправить топливом"}

 

  Implementation

Begin

Result:= (Fuel>0);

  end;                                            {Описание метода StartEngine.

 В качестве результата возвращается

значение логического выражения (Fuel>0)}

Function TCar.StopEngine:

Begin

end;                                             {Описание метода StopEngine,

не выполняющего никаких действий}

Procedure AddFuel (NewFuel: Double); {Описание метода AddFuel}

  Begin

Fuel:= Fuel + NewFuel; {Увеличение значения свойства

Fuel на величину, переданную

в качестве параметра NewFuel

при вызове метода}

  end;

End.                                             {Окончание модуля}

 

Обратим внимание на использование свойства Fuel в методе AddFuel. Метод AddFuel, который является частью описания класса TCar, обраща­ется к данному свойству по имени, хотя на этапе создания класса и неиз­вестно, какому именно экземпляру будет принадлежать изменяемое свойство. Однако точно известно, что каждый экземпляр обладает свойством Fuel, причем объект, из которого вызван метод AddFuel, также может быть определен системой. При вызове метода AddFuelH3 какого-либо экземп­ляра класса тсаг в качестве изменяемого свойства Fuel автоматически будет выбрано то, которое принадлежит этому же экземпляру.

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

 

Методы класса

 

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

Туре

       ………….

<Имя класса> = class

………

class   <Заголовок метода>; {Описание метода класса}

End;

При реализации методов класса в описательной части класса ключевое слово class также указывается:

class <Имя класса>.<Заголовок метода>;

 

Методы класса применяются для создания библиотек специализированных методов, не требующих доступа к каким-либо данным, которые могут храниться в пределах объекта. В качестве примера приведем описание класса MthdClass, метод Get5  которого является методом класса (листинг 5.2).

 

Листинг 5.2. Пример описания методов класса

 

Interface

Type

MthdClass = class

End;

Implementation

End.

 

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

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

 Использование метода класса отражено в следующем листинге 5.3.

 

Листинг 5.3. Использование метода класса

Unit UsingClassMethods;

Uses DeclaringClassMethods;

{Подключаем модуль DeclaringClassMethods,

в котором описан класс MthdClass}

  Interface

 Implementation

Procedure UsingClass; {Описание процедуры UsingClass}

  Var

A: Integer;   {Описание целочисленной переменной А}

Begin

A:= MthdClass. Get 5; {Вызов метода класса по имени класса

и метода, в переменную А заносится

значение 5}

End;

End.

 

Отметим, что методы класса иногда называют статическими методами.

 

Перегружаемые методы

 

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

Листинг 5.4. Описание класса с перегружаемыми методами

Туре

AClass = class

 Function Mult(A, B: Integer): Integer; overload;

{Заголовок метода Mult с двумя целочисленными параметрами, возвращающий целочисленное значение}

  Function Mult(A, В: Double): Double; overload;

{Заголовок метода Mult с двумя вещественными параметрами, возвращающий вещественное значение}

end;

Function AClass.Mult(A, B: Integer): Integer;

Begin

Result:= A * B;

end;                {Описание первого варианта метода Mult}

Function AClass.Mult(A, B: Double): Double;

Begin

Result: = A * B;

end;                 {Описание второго варианта метода Mult}

 

Области видимости элементов класса

 

Для разграничения доступа к свойствам и методам экземпляров классов между различными частями программы предусмотрены модификаторы доступа («видимости»), приведенные в табл. 5.1.

 

Модификатор доступа в Delphi (как и в Pascal, но в отличие от некото­рых других языков программирования) относится не к конкретному свой­ству или методу класса, а ко всем элементам класса (свойствам и методам), описание которых располагается после указания модификатора. Один и тот же модификатор может указываться в описании класса более одного раза.

Сравнительная таблица модификаторов доступа в Pascal и Delphi                Таблица 5.1

Модификатор

Туре

           ……………………..

<Имя класса> = class

Private

<Имя свойства 1>: <Тип свойства 1>;

{Описание свойств класса, имеющих область видимости private}

………………..

<Имя свойства N>: <Тип свойства N>;

<Заголовок метода 1>;

{Описание методов класса, имеющих область видимости private}

<3аголовок метода М>;

Protected

<Имя свойства 1>: <Тип свойства 1>;

{Описание свойств класса, имеющих область видимости protected}

………………..

<Имя свойства N>: <Тип свойства N>;

<Заголовок метода 1>;

{Описание методов класса, имеющих область видимости protected}

………………..

<Заголовок метода М>;

  public

<Имя свойства 1>: <Тип свойства 1>;

{Описание свойств класса, имеющих-область видимости public}

………………..

<Имя свойства N>: <Тип свойства N>;

<Заголовок метода 1>;

{Описание методов класса, имеющих область видимости public}

………………..

<Заголовок метода М>;

Published

......... 

                 {Описание специальных свойств

класса (property), имеющих область видимости published}

End;

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

Первый подход (см. листинг 5.5) состоит в том, чтобы скрыть свойства а и b от вызывающих подпрограмм, назначив им узкие области видимости, например private или protected. Выбор того или иного модификатора определяется необходимостью использования скрываемых свойств в классах-потомках данного класса. Если такой необходимости нет, то назначается область видимости private, если объекты-потомки должны иметь прямой доступ к свойствам, то назначается область видимости protected.

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

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

 

Листинг 5.5. Задание областей видимости. Первый подход

Unit UsingPrivatel;            {Заголовок модуля}

Interface                        {Начало интерфейсной части}

Туре

ABC = class            {Заголовок класса ABC}

private                    {Начало области видимости private)

a, b: Double;          (Свойства а и b имеют узкую область

видимости}

public                     {Начало области видимости public}

с: Double;                   {Свойство с имеет широкую область

  видимости}

Procedure SetA (NewA: Double);

{Метод SetK имеет широкую область видимости}

  Procedure SetB (NewB: Double);

{Метод SetB имеет широкую область видимости}

End;

Implementation                     {Начало описательной части}

Procedure ABC.SetA(NewA: Double);

{Описание метода SetA класса А В С}

Begin

а: = NewA;                        {Установка значения свойства а.]

с:= а * Ь;                       {Обновление значения свойства c-

поддержание целостности данных}

end;

Procedure ABC.SetB(NewB: Double);

{Описание метода SetB класса ABC

  Begin

b:= NewB;                        {Установка значения свойства b}

с:= a * b;                      {Обновление значения свойства с -

поддержание целостности данных}

End;

end.                                             {Окончание модуля}

 

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

 

Листинг 5.6. Задание областей видимости. Второй подход

Unit UsingPrivate2;          {Заголовок модуля}

 

Interface                    {Начало интерфейсной части}

 

Type

ABC 2 = class          {Заголовок класса ABC}

Private                {Начало области видимости private}

с: Double;            {Свойство с имеет узкую область видимости}

Public                 {Начало области видимости public}

a, b: Double;         {Свойства а и b имеют широкую область видимости}

Function GetC: Double;       {Метод GetC имеет широкую область видимости}

end;

 

Implementation               {Начало описательной части}

Function ABC2.GetC:Double;

{Описание метода GetC класса АВС2}

Begin

 с:= а* b;         {Обновляем значение свойства с}

Result:= с {      Возвращаем значение с вызывающей подпрограмме}

end;

end.                       {Окончание модуля}

 

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

 

Листинг 5.7. Задание областей видимости. Третий подход (оптимальный)

unit UsingPrivate 3;   {Заголовок модуля}

Interface

Type

АВСЗ = class

 private

a, b, c: Double;    {Все свойства имеют узкую область

видимости}

  public

Procedure SetA(NewA: Double);

{Все методы имеют широкую область видимости}

Implementation

Procedure ABC3.SetA(NewA: Double);

{Описание метода SetA класса АВСЗ}

  Begin

 a: = NewA;

с:= a * b;

 end;

Procedure АВСЗ.SetB(NewB: Double);

{Описание метода SetB класса АВСЗ}

Begin

 b:= NewB;

с:=. a * b;

end;

Function ABC3.GetC: Double;

{Описание метода GetC класса АВСЗ}

Begin

Result:= с;                   {Просто возвращаем значение с}

end;

 

End.

 

Property-свойства

 

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

 

Итак, описание свойств property в простейшем виде выглядит следующим образом:

property <Имя свойства>: <Тип> read <Функция чтения значения>

write <Процедура установки значения>;

 

Когда вызывающая подпрограмма обращается к свойству property для получения его значения, вместо конкретного значения возвращается результат функции, указанной по имени после модификатора read. Аналогично производится установка значения свойства — вместо прямой записи значения вызывается процедура, указанная после модификатора write. Соответственно, данная процедура должна принимать один параметр, причем его тип должен быть таким же, как тип самого свойства.

Модификаторы read и write могут не присутствовать в описании property-свойства одновременно. Если для свойства задана функция чтения, но не задана процедура записи, то такое свойство может быть использовано только для получения значения. Если задана процедура установки значения, но не задана функция чтения, то property-свойство можно использовать только в левой части оператора присваивания. В рассматриваемом нами примере свойства а и b должны быть доступны вызывающему фрагменту и для чтения и для записи, тогда как свойство с может быть доступно только для чтения.

Пример описания и использования property-свойства для поддержания корректности данных приведен в листинге 5.8.

Листинг 5.8. Пример описания и использования property -свойства

Unit Properties;                         {Заголовок модуля}

Interface

Type

ABC4 = class

Private

 fa, fb, fc: Double; {Все свойства имеют узкую область видимости}

protected                  {Все методы имеют область видимости protected}

Procedure SetA (NewA: Double); {Процедура установки значения свойства fa}

Procedure SetB (NewB: Double); {Процедура установки значения свойства fb}

 

Function GetA: Double; {Функция получения значения свойства fa}

Function GetB: Double;  {Функция получения значения свойства fb}

Function GetC: Double; {Функция получения значения свойства fc}

Published                    {Раздел описания property-свойств, доступных вызывающему фрагменту программы}

property A:Double read GetA write SetA;

{Описание property-свойства А, для которого задана функция чтения GetA и процедура установки SetA. Свойство будет доступно вызывающему фрагменту программы для чтения и записи}

  property B: Double read GetB write SetB;

{Описание property-свойства В, для которого задана функция чтения GetB и процедура установки SetB. Свойство будет доступно вызывающему фрагменту программы для чтения и записи}

Implementation

 Procedure ABC4.SetA(NewA: Double); {Описание метода SetA}

Begin

fa:= NewA;       {Занесение нового значения в private-свойство fa}

fc:= fa * fb;    {Пересчет произведения и занесение результата в свойство fc}.

end;

Procedure ABC4.SetB(NewB: Double);  {Описание метода SetB}

Begin

fb:= NewB;   {Занесение нового значения в private-свойство fb}

fc:= fa * fb;  {Пересчет произведения и занесение результата в свойство fc}

end;

Function ABC4.GetA: Double;              {Описание метода GetA}

Begin

Result:= fa;          {Результат функции — значение private-свойства fa}

end;

Function ABC4.GetB: Double;              {Описание метода GetB}

Begin

Result:= fb;             {Результат функции — значение private-свойства fb}

end;

Function ABC4.GetC: Double;              {Описание метода GetC}

Begin

Result:= fc; {Результат функции — значение private-свойства fc}

end;

end.                     {Окончание модуля}

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

Заметим, что вместо функции чтения и процедуры установки может, быть указано одно из обычных свойств, вне зависимости от его области видимости. В рассмотренном примере такую возможность следует применить при описании property-свойств а, b и с, указав вместо функций чтения свойства fa, fb и fc, соответственно. Модифицированный текст модуля приведен в листинге 5.9.

Листинг 5.9. Пример описания и использования property-свойства (модифицированный)

Unit Properties; {Заголовок модуля}

Interface

Type

ABC4 = class

 Private

fa, fb, fc: Double; {Все свойства имеют узкую область видимости}

protected        {Все методы имеют область видимости protected}

Procedure SetA (NewA: Double); {Процедура установки значения свойства fa}

  Procedure SetB (NewB: Double); {Процедура установки значения свойства fb}

  Published             {Раздел описания property-свойств, доступных вызывающему фрагменту программы}

property A: Double read fa write SetA; {Для установки значения property-свойства используется метод SetA, а для чтения private-свойство fa}

Implementation

Procedure ABC4.SetA(NewA: Double); {Описание метода SetA}

Begin

fa:= NewA;

fc:= fa * fb;

end;

Procedure ABC4.SetB(NewB: Double); {Описание метода SetB}

Begin

fb:= NewB;

fc:= fa * fb;

End;

end. {Окончание модуля}

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

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

 

5.2.1. Основы наследования

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

При описании дочернего класса с использованием наследования имя родительского класса указывается в скобках после ключевого слова с1ass| в заголовке интерфейсной части описываемого класса:

Туре

<Имя класса> = class (<Имя родительского класса>) {Заголовок описания}

......... {Описание собственных свойств и методов }

End;

Класс-наследник может быть описан на основе любого другого класса, вне зависимости от того, является ли родительский класс в свою очередь дочерним. Более того, все классы в Delphi являются наследниками от класса TObject, даже если это явно не указывается.

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

 

5.2.2. Переопределение методов

Часто встречается ситуация, когда один или несколько методов дочернего класса должны работать не так, как в родительском классе. В качестве примера можно привести класс TGeomFigure, представляющий собой абстрактную геометрическую фигуру, и имеющий метод Draw для ее рисования. Наследниками этого класса могли бы быть классы TCircle для работы с окружностями, TLine для работы с линиями, и так далее.

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

Механизм переопределения

 

Для переопределения метода, реализованного в объекте-родителе, следует:

 

♦ указать его заголовок в интерфейсной части описания дочернего класса без изменения имени, списка параметров и возвращаемого значения (если метод является функцией);

♦ указать после заголовка метода ключевое слово override. Если ключевое слово override не указано, то метод не переопределяется.

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

 

Чтобы метод мог быть переопределен в дочерних классах, он должен быть помечен ключевыми словами virtual или dynamic в интерфейсной части класса-родителя. Ключевое слово virtual указывает на то, что метод должен быть занесен в так называемую таблицу виртуальных методов ТВМ), а ключевое слово dynamic на то, что метод должен быть найден ло имени в дереве родительских объектов.

Разница между использованием virtual и dynamic заключается в направлении оптимизации компилятором вызовов переопределяемых методов. Методы, помеченные virtual, оптимизируются по скорости, а zynamic-методы по размеру программного кода. В большинстве случаев рекомендуется использование виртуальных методов, а использование динамических методов целесообразно при высоких степенях вложенности cвязей родитель-наследник.

Приведем описание класса TwoNums с двумя свойствами а и Ь, и методом GetResult, возвращающим сумму свойств. Далее, от этого класса эпишем наследника ThreeNums, имеющего уже три свойства — а, b и с, и переопределяющего метод GetResult таким образом, чтобы возвра­щать сумму не двух, а трех чисел (листинг 5.10).

Листинг 5.10. Использование переопределения

Unit Overriding 1;

Interface

Type

TwoNums = class

public                      {Заголовок класса TwoNums}

a, b: Integer;              {Описание двух свойств}

function GetResult: Integer; virtual; {Описание заголовка метода; после описания указано ключевое слово virtual, то есть этот метод может быть переопределен в дочернем классе}

End;

ThreeNums = class (TwoNums)

Public                        {Заголовок класса ThreeNums, в скобках после ключевого слова class указан класс-родитель}

с: Integer;              {Описание свойства с'. Свойства а и b

наследуются от класса-родителя TwoNums}

function GetResult: Integer; override;

{Описание заголовка метода, идентичного заголовку объекта-родителя; после описания указано ключевое слово override, указывающее на переопределение функциональности родительского метода}

End;

Implementation

function TwoNums.GetResult: Integer;

{Описание метода GetResult класса TwoNums}

Begin

Result:= a + b; {Результат функции — сумма двух свойств}

End;

function ThreeNums. GetResult: Integer; {Описание переопределенного метода

GetResult класса ThreeNums}

  Begin

Result:= a + b + c; {Результат функции — сумма трех свойств}

end;

End.

 

Переопределение методов с сохранением функциональности

 

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

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

Inherited  <Название метода>(<список параметров>);

Аналогично могут вызываться и переопределенные методы, которые являютсяфункциями:

….:= Inherited <Название метода>(<Список параметров>);

Изменим предыдущий пример таким образом, чтобы сумма свойств в методе GetResult вычислялась с использованием переопределенного вариантаэтого метода, описанного в классе TwoNums (листинг 5.11).

 

Листинг 5.11. Переопределение методов с сохранением функциональности

Unit  0verriding2;

Interface

Type

TwoNums = class

 public

a, b: Integer;

function GetResult: Integer; virtual;

 End;

ThreeNums = class (TwoNums)

 public

 c: Integer;

 function GetResult: Integer; override;

 End;

Implementation

Begin

Result: = Inherited GetResult + c;

{Результат функции — сумма значения, выдаваемого переопределенным методом (сумма а и Ь), и свойства с}

end;

End.

Var

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

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

Листинг 5.12. Описание и вызов конструктора

unit Geoml;

Interface

Type

TGeomFigure = class {Заголовок класса TGeomFigure, класс автоматически          является наследником класса TObject}

End;

Implementation

Procedure CreateFigure;

Var

Figure: TGeomFigure;{Описание переменной — ссылки на экземпляр класса. Несмотря на то, что не указан  модификатор ^,переменная Figure является переменной-указателем}

 B egin

Figure:= TGeomFigure. Create;{Вызов конструктора класса TGeomFigure, унаследованный от класса TObject, и сохранение ссылки на новый объект в переменной Figure}

end;

END.

 

Переопределение конструктора

 

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

1.Конструктор родительского класса не обязательно должен быть помечен ключевым словом virtual, для того, чтобы его переопределить. Соответственно, если конструктор родительского класса не помечен как виртуальный или динамический, не требуется указание ключевого слова override в описании дочернего класса. Виртуальные конструкторы необходимы для работы с типами данных, являющимися ссылками на классы (а не на объекты). Такая ссылка используется, например, в конструкторе класса TCollection, предназначенного в Delphi для хранения динамических списков.

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

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

Inherited;

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

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

Inherited Create (<Список параметров>);

Рассмотрим пример создания объекта с переопределением конструктора (листинг 5.13).

Листинг 5.13. Пример создания объекта с переопределением конструктора

unit   Geom2;

Interface

Type

TGeomFigure = class {Заголовок класса TgeomFigure, класс автоматически является наследником класса Tobject}

Protected

Color: Integer;       {Описание свойства с областью видимости protected)

Public

Constructor Create(aColor: Integer);

{Заголовок конструктора (область видимости public), список параметров изменен по отношению к родительскому классу TObject, конструктор которого не имеет параметров}

End;

Implementation

Constructor TGeomFigure. Create;

{Заголовок описательной части конструктора}

Begin

Inherited Create; {Вызов конструктора родительского класса}

Color:= aColor; {Инициализация свойства, которая не может быть выполнена в родительском классе, так как свойство Color описано в классе TGeomFigure}

end;

End.

 

5.3.2. Использование экземпляра класса

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

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

 ИЛИ

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

 Пример использования экземпляра класса показан в листинге 5.14.

Листинг 5.14. Пример использования экземпляра класса

unit Geom 3;

Interface

Type

TGeomFigure = class

Protected

Color: Integer;

 public

Implementation

......... {Описание конструктора}

......... {Описание метода Draw}

Procedure UsingFigure;   {Описание процедуры, использующей объект класса TGeomFigure}

  Var

Figure: TGeomFigure;

 begin

Figure:= TGeomFigure.Create(3); {Создание экземпляра класса TGeomFigure}

F



Поделиться:


Читайте также:




Последнее изменение этой страницы: 2021-04-04; просмотров: 49; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.222.80.122 (0.351 с.)