Привести примеры, оттестировать и продемонстрировать




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

Объектно-ориентированное программирование (ООП) зародилось в языках программирования Pascal, Smalltalk, Ада, С++.

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

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

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

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

Преимущества ООП по сравнению с традиционными способами программирования:

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

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

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

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

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

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

Объект

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

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

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

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

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

Классами в Delphi называются специальные типы, которые содержат поля, методы и свойства.

Определение класса находится в разделе Type глобального блока, за которым следует имя класса, его поля и методы и свойства. Завершается описание словом end.

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

Таким образом, объявление класса может содержать:

· поля (как в типе данных запись),

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

· методы (подпрограммы, которые обрабатывают поля и свойства класса).

Пример.

Type

TPeople = Class

Name: String;

Age: 0..99;

Procedure GetName;

Procedure GetAge;

end;

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

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

type

TFirstObject = class;

TSecondObject = class (TObject)

First: TFirstObject;

end;

TFirstObject = class(TObject)

end;

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

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

Важным отличием классов от других типов является то, что объекты класса всегда располагаются в куче (Heap - куча), поэтому объектная переменная фактически представляет собой лишь указатель на динамическую область памяти. Однако, при ссылке на содержимое объекта символ "^" не ставится:

Var

People: TPeople;

Begin

People ^. Age:= 20; //ошибка описания

People.Age:= 20;

End;

 

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

Такое объединение в классе данных и подпрограмм для их обработки называется инкапсуляцией.

Данные содержатся в полях класса, а процедуры и функции для их обработки называют методами.

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

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

Например,

класс TForm содержит (инкапсулирует в себе) все необходимое для создания Windows-окна;

класс ТМеmо представляет собой полнофункциональный текстовый редактор;

класс TTimer обеспечивает работу программы с таймером и т.д.

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

Библиотека классов Delphi– это, фактически, набор «кирпичиков», созданных программистами Borland для построения программ пользователя.

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

Имя класса может быть любым допустимым идентификатором. Принято идентификаторы большинства классов начинать с символа " Т ".

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

TButton – класс для создания виртуальных кнопок.

TEdit – класс для создания и управления работой строки ввода.

 

Чтобы описать объект, следует выполнить следующее объявление в разделе Var:

Например,

Var

People: TPeople;

При работе с обычными типами данных этого объявления было бы достаточно для получения экземпляра типа. Однако объекты среды Delphi являются динамическими данными, т.е. распределяются в динамической памяти. Поэтому переменная People – это просто ссылка на экземпляр (объект в памяти), которого физически еще не существует. Чтобы сконструировать объект (выделить память для экземпляра) класса TPeople и связать с ним переменную People, нужно в тексте программы поместить следующий оператор:

People = TPeople.Create; //выделение памяти под объект

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

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

Например,

People.GetName;

People.GetAge;

или

with People do

begin

GetName;

GetAge;

End;

Если объект становится ненужным, он должен быть удалён вызовом специального метода Destroy.


Например:

People.Destroy; //Освобождение памяти, занимаемой объектом

 

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

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

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

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

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

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

 

TSecondClass = Class (TObject);

TSecondClass = Class;

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

TNewClass = class (TOldClass)

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

Принцип наследования приводит к созданию ветвящегося дерева классов, постепенно разрастающегося при перемещении от TObject его потомкам. Каждый потомок дополняет возможности своего родителя новыми, и передает их своим потомкам.

Пример. Приведем небольшой фрагмент дерева классов Delphi.



Класс TPersistent дополняет возможности родителя – класса TObject: он «умеет» сохранять данные в файле и получать их из него, в результате это умеют делать и все его потомки.

Класс TComponent, позволяет взаимодействовать со средой разработчика и передает это умение своим потомкам.

TControl не только способен работать с файлами и средой разработчика, но он еще умеет создавать и обслуживать видимые на экране изображения, а его потомок TWinControl может создавать Windows-окна и т.д.

 

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

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

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

Пример. Пусть имеется описание родительского класса TBase и его потомка TDescedant, содержащих одноименный метод MyJoy:

 

Type

TBase = class

Procedure MyJoy;

end;

TDescedant = class(TBase)

Procedure MyJoy;

end;

Var

FirstObject:TBase;

SecondObject:TDescedant;

Begin

……….

FirstObject.MyJoy;

SecondObject.MyJoy;

……….

End;

В соответствии с принципом полиморфизма в операторе

FirstObject.MyJoy;

вызывается метод, описанный в классе TBase.

А в операторе

SecondObject.MyJoy;

вызывается метод, описанный в классе TDescedant.


Составляющие класса

В общем виде класс объявляется в разделе Type следующим образом:

Type

< имя класса > = class (<имя класса-родителя>)

public

<описание общедоступных элементов>

published

<описание элементов, доступных в Инспекторе Объектов>

protected

<описание элементов, доступных в классах-потомках>

private

<описание элементов, доступных только в модуле>

end;

Секции public, published, protected, private могут содержать описания полей, методов, свойств и событий.

 

Поля

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

Type

TChildClass = class

FOne: Integer;

FTwo: String;

FThree:TObject;

end;

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

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

Var

MyObject:TChildClass;

Begin

MyObject.FOne:= 16;

MyObject.FTwo:= 'Некоторое строковое значение';

End;

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

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

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


Свойства

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

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

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

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

Для объявления свойства используются зарезервированные слова property, read, write.

Слова read и write обозначают начала разделов, содержащих имена методов, предназначенных для чтения и записи соответственно.

Например,

Type

TStudent = class

FAge: integer;

Function GetAge: integer;

Procedure SetAge (Value: integer);

property Age: integer read GetAge write SetAge;

end;

 

Здесь Age – свойство, связанное с полем FAge;

GetAge и SetAge – методы, предназначенные соответственно для чтения и записи в поле Age.

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

Например,

Var

GoodStudent: TStudent;

HisAge: Integer;

Begin

GoodStudent:= TStudent.Create; // порождение объекта

GoodStudent.Age:= 19;

……………..

HisAge:= GoodStudent.Age;

………………

GoodStudent.Free;

End;

 

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

GoodStudent.FAge:= 19;

Но, используя свойство Age, можно реализовать в методах, предназначенных для чтения и записи GetAge и SetAge, различные дополнительные действия: проверку вводимых значений на принадлежность к заданному диапазону, выдачу сообщений на экран, изменение внешнего вида объекта на экране и т.д.

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

type

TStudent = class

FAge: integer;

Procedure SetAge (Value: integer);

property Age: integer read F Age write SetAge;

end;

Поля могут быть доступны только при чтении или только при записи. В этом случае при описании свойства опускаются разделы read или write. Например, если требуется, чтобы свойство Age было доступным только для чтения, нам необходимо убрать раздел write:

Type

TStudent = class

FAge: integer;

Function GetAge: integer;

property Age: integer read GetAge;

end;

в самом простейшем случае реализация свойств может быть такой:

Implementation

{$R *.dfm}

Function TStudent.GetAge: integer;

begin

result:=FAge;

end;

Procedure TStudent.SetAge (Value: integer);

begin

FAge:=Value;

end;

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

Методы

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

Например,

Type

TChildClass = class

FOne: Integer;

FTwo: String; поля класса

FThree:TObject;

 
 


Function FirstFunc (x: real): real; методы класса

Procedure SecondProc;

end;

 

Для обращения к методам, как и к полям, необходимо использовать составные имена:

Var

MyObject:TChildClass;

y: real;

Begin

……….

MyObject.SecondProc;

y:= MyObject.FirstFunc (3.14);

……….

End;

Процесс определения методов объектов напоминает создание модулей в OР.

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

Type

TPerson = Class

FName: string[30];

FDate: String [10];

Procedure Init (Nm, Dt: String); //только заголовок

Function GetName: String; //только заголовок

Function GetDate: String; //только заголовок

End;

Поля данных должны быть объявлены перед объявлением методов.

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

Сами методы описываются вне определения класса как отдельная процедура или функция.

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

Procedure TPerson. Init (Nm, Dt: String);

Begin

FName: = Nm;

FDate:=Dt;

end;

Function TPerson. GetName: String;

Begin

GetName: =FName;

end;

Function TPerson. GetDate: String;

Begin

GetDate: =FDate;

end;

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

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

Для статических методов перекрытие осуществляется компилятором.

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

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

В классе-потомке в заголовке замещающего метода должно быть указано зарезервированное слово override.

Например,

Type

TBase = class

Procedure MyJoy; virtual;

end;

TDescedant = class(TBase)

Procedure MyJoy; override;

end;

 

Var

FirstObject:TBase;

SecondObject:TDescedant;

Begin

……….

FirstObject.MyJoy;

SecondObject.MyJoy;

……….

End;

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

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

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

Procedure MyMetod; virtual; abstract;

Основное предназначение абстрактных методов – быть родоначальником иерархии конкретных методов в классах-потомках.

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

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

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

Пример.

Type

TlObj = class

FExtData: Extended;

procedure SetData (AValue: Extended);

end;

T2Obj = class(TlObj)

FIntData: Integer;

procedure SetData (AValue: Integer);

end;

var

Tl: TlObj;

T2:T2Obj;

В этом случае попытка вызова из объекта Т2 методов

...

Т2.SetData (1.0);

Т2.SetData(1);

...

вызовет ошибку компиляции на первой из двух строк.

Для компилятора внутри Т2 статический метод с параметром типа extended перекрыт, и он его " не признает ".

Можно было бы переименовать один из методов, например, создать SetIntegerData и SetExtendedData. Но если методов не два, а, скажем, сто, возникнет путаница. Сделать методы виртуальными нельзя, поскольку тип и количество параметров в одноименных виртуальных методах должны в точности совпадать. Для этого существуют перегружаемые методы, объявляемые при помощи директивы overload:

type

TlstObj = class

FExtData: Extended;

procedure SetData(AValue: Extended); overload;

end;

T2ndObj = class(TlstObj)

FIntData: Integer;

procedure SetData (AValue: Integer); overload;

end;

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

Можно перегрузить и виртуальный (динамический) метод. Надо только в этом случае добавить директиву reintroduce:

type

TlObj = class

FExtData: Extended;

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

end;

T2Obj = class (T1Obj)

FIntData: Integer;

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

end;

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

 

Пример2.

T1 = class (TObject)

procedure sum (x,y:integer); overload;

end;

T2 = class (T1)

procedure sum(x,y:real); overload;

end;

procedure T1.sum (x,y:integer);

Begin

ShowMessage (IntToStr (x+y));

end;

procedure T2.sum (x,y:real);

Begin

ShowMessage (FloatToStr (x+y));

end;

Var

tt1: T1;

tt2:T2;

x, y: integer;

x1, y1: real;

Begin

tt1:=t1.Create;

tt2:=t2.Create;

x:=45;

y:=123;

tt1.sum(x, y); // вызывается метод класса Т1

x1:=45.56;

y1:=123.456;

tt2.sum(x1, y1); // вызывается метод класса Т2

end;

 

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

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

Разрушение – очистку полей и освобождение памяти.

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

В классе TObject и в большинстве его потомков конструктор и деструктор называются Create и Destroy соответственно. Можно даже определить несколько конструкторов и деструкторов (имена им назначает сам программист), чтобы обеспечить различные процедуры создания и разрушения объектов.

Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированных слов function и procedure используются слова constructor и destructor.

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

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

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

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

Например,

Type

TSample = class

Text: String;

Constructor Create;

Destructor Destroy;

End;

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

 

Var

MyObject:TSample;

begin

………….

MyObject:=TSample.Create;

………….

End;

Create – это конструктор объекта; он всегда присутствует в классе и служит для создания и инициализации экземпляров.

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

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

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

Если конструктор применяется к объекту,

MyObject.Create;

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

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

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

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

Type

TDescedant = class (TBase)

FMark: Boolean;

Constructor Create (Mark: Boolean);

End;

то его реализация могла быть такой:

Constructor TDescedant.Create (Mark: Boolean);

Begin

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

FMark:=Mark; //осуществление дополнительных действий

End;

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

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

Inherited Click;

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

MyObject.Destroy; // Деструктор освобождает динамическую память и разрушает объект.

Destroy – деструктор объекта, присутствует в классе наряду с конструктором и служит для удаления объекта из динамической памяти.

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

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

Пример:

if MyObject <> nil then

begin

MyObject.Destroy;

MyObject:=nil;

End;

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

Чтобы избавить программистов от лишних ошибок, в объекты кроме деструктора Destroy ввели метод Free, который следует вызывать вместо деструктора.

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

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

MyObject.Free;

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

MyObject.Free;

MyObject:= nil;

С помощью стандартной процедуры FreeAndNil это можно сделать проще и элегантнее:

FreeAndNil (MyObject);

Эквивалентно:

MyObject. Free;

MyObject:=nil;

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

Var

MyObject1, MyObject2: TSample;

Begin

MyObject1:= TSample.Create;

MyObject2:= MyObject1; // ПРОВЕРИТЬ!!!

MyObject1.Free;

end;

 

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

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

Например,

Type

TChildClass = class (TObject)

class function ChildClassInfo: string;

End;

// описание метода класса

class function TChildClass.ChildClassInfo: string;

Begin

End;

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

Var

Y: string;

Begin

……………..

Y:= TChildClass. ChildClassInfo;

……………..

End;

Пример 2.

Type

TMyObject= class (TObject)

class function GetSize: string;

end;

Var

MyObject: TMyObject; // экзем



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-03 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: