Устаревшее его использование (не показан в вышестоящем синтаксисе), использовалось, чтобы создать объект (теперь мы используем конструктор класса).
Переменные функции и типы процедуры могут использоваться как указатели к функциям и процедурам с теми же самыми параметрами и конфигурацией возвращаемого значения (сигнатура).
function AddUp(a, b : Integer) : Integer; . type TFunc = function(a, b : Integer) : Integer; var func : TFunc; c : Integer; begin func := AddUp; c := func(12, 34); // Вызывает функцию AddUp end;
Со спецификатором Of Object, тип подпрограммы должен быть установлен на указатель метода в объекте.
type TMyClass = class public procedure StoreName(name : string); end;
TProc = procedure(name : string) of object; var proc : TProc; myClass : TMyClass; begin myClass := TMyClass.Create; proc := myClass.StoreName; proc(‘My name’); // Вызывает myClass.StoreName end;
Примечания
Такие типы подпрограмм в действительности яявляются указателями и на код и на части данных метода.
Похожие команды
Class Начинает объявление типа объектного класса
Function Определяет подпрограмму, которая возвращает значение
Procedure Определяет подпрограмму, которая не возвращает значение
TObject Тип базового класса, который является предком для всех других классов
Delphi что такое объект
С этого урока мы начнем осваивать так называемое объектно-ориентированное программирование (ООП). ООП — идеология программирования, основанная на объединении данных и процедур, которые могут работать с этими данными в совокупности, называемые классами. Ранее мы вкратце упоминали о них, и теперь пришло время в них разобраться. Сутью ООП является использование привычной нам в обыденной жизни объектной модели. Каждый объект имеет свои свойства и с ним можно совершить характерные для него действия. Класс — это тип объекта. Класс описывает и реализует те самые свойства и действия. Объект в нашем понимании будет являться переменной, типом который и будет являться какой-то класс. Полями класса мы будем называть его свойства, а методами — действия, которые можно совершить с экземпляром этого класса (объектом). В некоторой степени классы можно сравнить с обыкновенными записями, которые мы рассмотрели в прошлом уроке. Однако классы имеют гораздо больше возможностей, с которыми мы будем знакомиться постепенно на протяжении большого количества уроков.
Прежде чем создавать какие-то объекты, давайте создадим хотя бы один тип объектов, т.е. класс. Класс описывается в разделе type следующим образом: [cc lang=»delphi»]type TBook = class public PagesCount: integer; Title, Author: string;
function CompareWithBook(OtherBook: TBook): integer; procedure ShowTitle;
Вы уже наверное догадались, что наши методы, как и обычные процедуры и функции, Установим курсор на названии любого метода и нажмем сочетание клавиш Ctrl+Shift+C. Это создаст нам пустые шаблоны реализаций всех методов класса. Выглядеть это будет примерно следующим образом: [cc lang=»delphi»]
function TBook.CompareWithBook(OtherBook: TBook): integer; begin
constructor TBook.Create(NewTitle, NewAuthor: string; NewPagesCount: integer); begin
procedure TBook.ShowTitle; begin
end; [/cc] Начнем с реализации самого интересного — конструктора. Как уже упоминалось выше, внутри конструктора мы должны присвоить некоторым полям определенные значения (не обязательно всем) и создать все нужные нам вложенные объекты. Т.к. вложенных объектов внутри нашего класса нет, да и рассматривать создание объекта (экземпляра класса) мы будем чуть позже, то нам нужно только присвоить полям Title, Author и PagesCount нужные значения. Эти значения мы будем передавать при помощи параметров конструктора. Однако в конструкторе (в отличие от нашего примера) мы можем и не присваивать значения всем полям. Нетрудно догадаться, каким образом будет выглядеть реализация конструктора: [cc lang=»delphi»]constructor TBook.Create(NewTitle, NewAuthor: string; NewPagesCount: integer); begin Title := NewTitle; Author := NewAuthor; PagesCount := NewPagesCount; end;[/cc] Я уже говорил, что внутри методов класса мы имеем доступ ко всем другим полям и методам данного класса (за исключением описанных внутри специальных модификаторов доступа, их мы будем рассматривать позже). Так вот и в конструкторах (как и в методах) мы можем работать с полями класса как с локальными переменными. Этим мы и воспользовались — присвоили полям класса изначальные значения. Теперь реализуем метод CompareWithBook: [cc lang=»delphi»]function TBook.CompareWithBook(OtherBook: TBook): integer; begin Result := Abs(OtherBook.PagesCount — PagesCount); end;[/cc] В этом примере мы получаем модуль разности количества страниц другой книги и данной. В нашем случае OtherBook — это объект типа/класса TBook. Т.е. OtherBook — является экземпляром класса TBook. Методу одной книги CompareWithBook, который мы будем использовать, мы будем передавать в качестве параметра другую книгу типа TBook. И соответственно к своему полю PagesCount из метода данного класса можно обратиться как и к локальной переменной. Также, мы можем обращаться к своим полям и методам при помощи зарезервированной переменной Self: [cc lang=»delphi»]Result := Abs(OtherBook.PagesCount — Self.PagesCount);[/cc] Ну и наконец реализуем метод ShowTitle. Этот метод будет выводить в сообщении ShowMessage автора книги и название в кавычках. Еще один наглядный пример обращения к полям класса: [cc lang=»delphi»]procedure TBook.ShowTitle; begin ShowMessage(Author + ‘ «‘ + Title + ‘»‘); end;[/cc]
После того как мы описали класс и реализовали все его методы, мы можем использовать наш класс, а именно создавать экземпляры этого класса — объекты. Сначала опишем объект в разделе var как обыкновенную переменную: [cc lang=»delphi»]var MyBook: TBook;[/cc] Затем приступим к созданию объекта. Для того чтобы использовать экземпляр класса, просто объявить его недостаточно. Его нужно создать. Если объект не создать, то при первом обращении к какому-либо полю или методу класса, неминуемо будет вызвана ошибка «Access Violation». Поэтому научимся создавать объекты: [cc lang=»delphi»]MyBook := TBook.Create(‘Delphi для начинающих’, ‘Cyberexpert’, 1000);[/cc] Воспользуемся нашим конструктором Create. Как вы видите, мы должны присвоить нашему объекту конструктор создаваемого класса. При необходимости конструктору нужно передать параметры. Конструкторов у класса может быть несколько. Вот теперь мы можем использовать наш объект так как нам нужно. Давайте создадим еще одну книгу: [cc lang=»delphi»]MyBook2 := TBook.Create(‘Delphi для начинающих 2’, ‘Cyberexpert’, 1300);[/cc] И попробуем сравнить эти книги при помощи нашего метода CompareWithBook у класса TBook. Для этого вызовем этот метод у одного объекта, и в качестве параметра передадим другой объект типа TBook: [cc lang=»delphi»]a := MyBook.CompareWithBook(MyBook2); ShowMessage(IntToStr(a));[/cc] Можем также вывести на экран автора и название книги: [cc lang=»delphi»]MyBook.ShowTitle;[/cc] Предположим мы каким-то образом использовали наш объект и теперь он стал нам не нужен. Зачем ему занимать лишнюю оперативную память? Давайте его удалим! Сделать это можно при помощи деструктора. У каждого класса по-умолчанию помимо конструктора Create существует деструктор Destroy. Т.е. их не надо описывать и создавать, у каждого класса они уже есть. Только в нашем нестандартном конструкторе мы реализовали присвоение полям начальных данных. Если бы мы не создали свой конструктор, то использовали бы стандартный при создании объекта, и изначальных параметров передать не смогли бы. Также можно создать и деструктор, давайте его сделаем и будем в нем выводить некоторое сообщение: [cc lang=»delphi»] TBook = class public PagesCount: integer; Title, Author: string;
function CompareWithBook(OtherBook: TBook): integer; procedure ShowTitle;
constructor Create(NewTitle, NewAuthor: string; NewPagesCount: integer); destructor Destroy; // так деструктор будет выглядеть в описании класса end;[/cc] Реализация деструктора: [cc lang=»delphi»]destructor TBook.Destroy; begin ShowMessage(‘Уничтожается книга «‘ + Title + ‘»‘); end;[/cc] Т.е. мы будем выводить сообщение перед уничтожением объекта. Для чего же еще нужен деструктор? Для того чтобы уничтожать также и все вложенные в классе объекты. Т.е. когда внутри класса существует объект, чтобы его использовать, мы должны этот «подобъект» создать в конструкторе и уничтожить в деструкторе. Соответственно, чтобы вызвать деструктор мы вызовем его как обыкновенный метод тогда, когда нам нужно удалить объект: [cc lang=»delphi»]MyBook.Destroy;[/cc] В этом уроке мы рассмотрели самые базовые основы ООП. В следующих уроках мы продолжим знакомиться с классами и их возможностями. Исходный код проекта, созданного в этом уроке, можно скачать здесь.
Delphi создание объекта
Delphi 7. Занятие 2_7. Часть 3.
Как отмечалось ранее, экземпляры объектов создаются с помощью статического метода класса «create». Далее рассмотрим различные варианты применения этого метода.
Delphi создание экземпляра класса
Определим два новых класса.
Первый класс наследуем от TObject. Добавляем поле ii:integer;.
TMyClassA=class(TObject) //если наследование идёт от TObject, то можно просто записать TMyClassA=class
ii:integer;
Второй класс наследуем от первого и добавляем поле ss:string;
TMyClassB=class(TMyClassA)
ss:string;
В обработчике клика по кнопке создаём экземпляры объектов этих классов и выводим интересующие нас значения:
procedure TForm1.Button1Click(Sender: TObject);
var vA:TMyClassA; vB:TMyClassB;
vA:=TMyClassA.Create;
vB:=TMyClassB.Create;
В результате будем иметь:
В поле 1) отображено значение поля ii объекта vA.
В объект vB введено поле ss, а также унаследовано поле ii. Значение поля ii объекта vB отображено в поле 2).
В поле 3) отображено значение поля ss объекта vB.
В полях 4,5,6 и 7 отображено значение, возвращаемое функцией класса ClassName, вызываемой как непосредственно ссылкой из класса (значения 4 и 5), как и через экземпляр класса (значения 6 и 7).
В приведённом примере при создании экземпляра объекта vA вызывался конструктор из класса-родителя TObject.
Для класса TMyClassB родителем является класс TMyClassA. Так как в классе TMyClassA конструктор явно не задан, то берётся конструктор от его родителя — класса TObject.
Delphi конструктор класса
Пусть теперь в первом классе, порожденном от TObject, задан свой конструктор create.
constructor create;
Реализация тела конструктора:
constructor TMyClassC.create;
kk:=30; //задаём начальное значение поля;
procedure TForm1.Button2Click(Sender: TObject);
var vC:TMyClassC; vD:TMyClassD;
Как видно из кода, после создания экземпляра объекта vC его полю kk значение явно не присваивается.
Явно присваиваются значения полям kk и st объекта vD.
Вызов конструктора для класса TMyClassD.create влечёт за собой вызов конструктора класса-родителя TMyClassC:
constructor TMyClassC.create;
kk:=30; //задаём начальное значение поля;
Прежде, чем начнёт выполняться тело конструктора TMyClassC.create, будет вызван конструктор класса-родителя (то есть конструктор из TObject) и будет создан объект vD.
После создания облъекта явно заданные конструкторы начнут просматриваться в обратном порядке и в них будет выполнен код, содержащийся в их описаниях.
В данном случае выполнится инициализация поля kk:=30;
Это видно из приведенного примера:
Хотя полю vD.kk значение явно не задаётся, однако его определяет конструктор класса-родителя.
Если добавить оператор:
то результат будет следующим:
Теперь создадим оба класса с конструкторами.
constructor create;
constructor create;
constructor TMyClassG.create;
constructor TMyClassH.create;
И создание объектов:
procedure TForm1.Button4Click(Sender: TObject);
var vG:TMyClassG; vH:TMyClassH;
Поля 1, 2 и 3 сформированы конструктором. Поля 4, 5, и 6 отображают значения, заданные непосредственно для экземпляров объектов.
Теперь рассмотрим ситуацию:
constructor TMyClassM.create;
constructor TMyClassN.create;
procedure TForm1.Button5Click(Sender: TObject);
var vM:TMyClassM; vN:TMyClassN;
Оба класса имеют конструктор. Но конструктор класса TMyClassM определяет значение поля: nn:=60;. А в конструкторе TMyClassN этого поля нет. Тогда будем иметь следующий результат:
constructor TMyClassN.create;
поле nn не инициализируется, и начальное значение не передаётся из конструктора родительского класса.
Delphi inherited
Чтобы исправить ситуацию, надо использовать конструктор класса delphi, унаследованный от класса-родителя.
Для этого необходимо использовать ключевое слово «inherited» (унаследованный):
constructor TMyClassN.create;
inherited create; // вызываем конструктор, унаследованный от TMyClassM;
В результате будем иметь:
Наконец, рассмотрим конструктор с параметрами.
constructor create(n:integer=5);
constructor create(s:string;nm:integer=88);
constructor TmyClassP.create(n:integer=5); //параметр имеет значение по умолчанию
constructor TMyClassQ.create(s:string;nm:integer=88); //параметр имеет значение по умолчанию
procedure TForm1.Button6Click(Sender: TObject);
var vP:TMyClassP; vQ:TMyClassQ;
В результате имеем:
Так как параметры конcтруктора имеют значения по умолчанию, организуем вызов конструкторов в следующем виде:
Классы delphi
Delphi 7. Занятие 2_7. Часть 2.
Классы дочерние и родительские
В части 1 говорилось о механизме наследования. Рассмотрим его действие на примере. Будем создавать иерархию классов, начиная от базового класса TObject.
Посмотрим на методы базового класса TObject и дадим комментарии к некоторым из них.
Во первых, это методы, которые даже сгруппированы в отдельный класс TClass.
Далее дан полный перечень методов класса TObject.
Если посмотреть внимательно, то можно увидеть, что методы класса TClass являются составной частью класса TObject, но помечены директивой «class»:
Просмотрев содержание класса TObject, можно выделить четыре группы записей.
1) конструктор «constructor Create» и деструктор «destructor Destroy».
2) Функции и процедуры, помеченные директивой «virtual» (а также директивой «dinamic»; в классе TObject она отсутствует).
3) Функции и процедуры, которым предшествует ключевое слово class.
4) Функции и процедуры, не помеченные директивами «dinamic», «virtual» и class.
Функции и процедуры, не помеченные директивами «dinamic» и «virtual», называются статическими.
Под статические процедуры и функции распределение памяти происходит при компиляции программы. То есть, использовать такие функции и процедуры можно, не создавая экземпляр объекта данного класса в динамической памяти.
Рассмотрим пример. Создадим новый класс на основе класса TObject.
Type
ii:integer; //ввели новое поле, которого нет в TObject.
Объявим переменную класса TMyClass и посмотрим, какие методы оказываются доступны без создания экземпляра объекта.
Мы видим, что можно напрямую обратиться к функциям и процедурам, имеющим директиву «class» (о директиве «class» смотри выше).
Замечание.
Компилятор не отводит память под поле ii, хотя наличие поля отображается в составе класса. Память под поля объекта будет распределена при создании экземпляра объекта.
Действительно, экземпляров данного класса может быть создано несколько, и у каждого может быть собственное значение поля.
Проиллюстрируем доступ к статическим методам без создания экземпляра объекта.
При обращении к методу «ClassName» объект «x» ещё не создан. В этом случае обращение к методу класса через переменную возвращает тип того объекта, из которого вызван метод. Метод вызван из обработчика Button4Click, поэтому возвращается значение Tbutton.
Второй обработчик — обработчик клика по Edit1.
procedure TForm1.Edit1Click(Sender: TObject);
var x:TObject;
form1.Edit3.Text:=x.ClassName;
Метод вызван из обработчика Edit1Click, поэтому возвращается значение «Tedit».
Обращение к функции ClassName через класс TMyClassCвозвращает имя класса «TMyClassC».
Delphi конструктор класса
Однако надо помнить, что главной статической процедурой является конструктор Create.
Сама процедура Create ничего не делает (тело процедуры пустое), но служит основой для переопределения в потомках. Однако её вызов автоматически влечёт за собой вызов функции «_ClassCreate()». Внутри неё осуществляются ассемблеровские операции по распределению памяти под объект и вызов функции InitInstance.
В результате создаётся экземпляр объекта и возвращается указатель на него.
После создания объекта открывается доступ к его полям и нестатическим методам. Все поля инициируются нулевыми или пустыми значениями.
Если поле является ссылкой на объект, то ему присваивается значение nil.
Таким образом, основное предназначение статических методов TObject — создание экземпляра объекта.
Непосредственно к методам TObject как правило, не обращаются.
Экземпляр класса
var x:TObject;
и вызывая конструктор
мы создаём экземпляр объекта и получаем доступ к его полям (либо напрямую, либо через свойства; о свойствах объекта речь пойдёт далее). Также мы получаем доступ не только к статическим, но также к динамическим и виртуальным методам.
Также мы получаем возможность наполнить содержанием методы, помеченные директивой «abstract»
После создания экземпляра объекта нам становится доступен весь список методов, полей и свойств экземпляра. В данном случае это методы объекта класса Tobject (полей у этого класса просто нет):
Delphi что такое объект
Оглавление
Сейчас преимущества использования объектов очевидны для всех. Однако так было не всегда. Сначала старая гвардия не поняла и не приняла объекты, поэтому они почти 20 лет потихоньку развивались в различных языках, первым из которых была Simula 67. Постепенно объектно-ориентированный подход нашел себе место и в более мощных языках, таких как C++, Delphi и множестве других языков. Блестящим примером реализации объектов была библиотека Turbo Vision, предназначенная для построения пользовательского интерфейса программ в операционной системе MS-DOS.
Полную победу объекты одержали с приходом эпохи многофункциональных графических пользовательских интерфейсов. Теперь без объектов в программировании просто не обойтись. Чтобы вы не рылись в других книгах, собирая информацию по крохам, мы не поленились и объединили в этой главе все, что нужно знать об объектах. Для новичка важнейшее здесь: инкапсуляция, наследование, полиморфизм, остальное можно просто просмотреть и возвращаться к материалу по мере накопления опыта. Профессионалу полезно прочитать внимательно все от начала до конца. Поэтому давайте засучим рукава и приступим к делу.
Краеугольные камни ООП
Формула объекта
Авторы надеются, что читатель помнит кое-что из главы 2 и такие понятия как тип данных, процедура, функция, запись для него не в новинку. Это прекрасно. Так вот, в конце 60-х годов кому-то пришло в голову объединить эти понятия, и то, что получилось, назвать объектом. Рассмотрение данных в неразрывной связи с методами их обработки позволило вывести формулу объекта:
Объект = Данные + Операции
Природа объекта
Об объектах можно думать как о полезных существах, которые «живут» в вашей программе и коллективно решают некоторую прикладную задачу. Вы, как Демиург, лепите этих существ, распределяете между ними обязанности и устанавливаете правила их взаимодействия.
В общем случае каждый объект «помнит» необходимую информацию, «умеет» выполнять некоторый набор действий и характеризуется набором свойств. То, что объект «помнит», хранится в его полях. То, что объект «умеет делать», реализуется в виде его внутренних процедур и функций, называемых методами. Свойства объектов аналогичны свойствам, которые мы наблюдаем у обычных предметов. Значения свойств можно устанавливать и читать. Программно свойства реализуются через поля и методы.
Например, объект «кнопка» имеет свойство «цвет». Значение цвета кнопка запоминает в одном из своих полей. При изменении значения свойства «цвет» вызывается метод, который перерисовывает кнопку.
Объекты и компоненты
Когда прикладные программы были консольно-ориентированными, а пользовательский интерфейс был простым, объекты казались пределом развития программирования, поскольку были идеальным средством разбиения сложных задач на простые подзадачи. Однако с появлением графических систем программирование пользовательского интерфейса резко усложнилось. Программист в какой-то мере стал дизайнером, а визуальная компоновка и увязка элементов пользовательского интерфейса (кнопок, меток, строк редактора) начали отнимать основную часть времени. И тогда программистам пришла в голову идея визуализировать объекты, объединив программную часть объекта с его видимым представлением на экране дисплея в одно целое. То, что получилось в результате, было названо компонентом.
В данной главе мы рассмотрим лишь вопросы создания и использования объектов. Чуть позже мы научим вас превращать объекты в компоненты (см. главу 13).
Классы объектов
Три кита ООП
Весь мир ООП держится на трех китах: инкапсуляции, наследовании и полиморфизме. Для начала о них надо иметь только самое общее представление.
Классы
Классы объектов определяются в секции type глобального блока. Описание класса начинается с ключевого слова class и заканчивается ключевым словом end. По форме объявления классы похожи на обычные записи, но помимо полей данных могут содержать объявления пользовательских процедур и функций. Такие процедуры и функции обобщенно называют методами, они предназначены для выполнения над объектами различных операций. Приведем пример объявления класса, который предназначен для чтения текстового файла в формате «delimited text» (файл в таком формате представляет собой последовательность строк; каждая строка состоит из значений, которые отделены друг от друга символом-разделителем):
Класс содержит поля (FileVar, Items, Delimiter) и методы (PutItem, SetActive, ParseLine, NextLine, GetEndOfFile). Заголовки методов, (всегда) следующие за списком полей, играют роль упреждающих (forward) описаний. Программный код методов пишется отдельно от определения класса и будет приведен позже.
Класс обычно описывает сущность, моделируемую в программе. Например, класс TDelimitedReader представляет собой «читатель» текстового файла с разбором считываемых строк на элементы (подстроки), которые отделены друг от друга некоторым символом, называемым разделителем.
Класс содержит несколько полей:
Класс также содержит ряд методов (процедур и функций):
Обратите внимание, что приведенное выше описание является ничем иным, как декларацией интерфейса для работы с объектами класса TDelimitedReader. Реализация методов PutItem, SetActive, ParseLine, NextLine и GetEndOfFile на данный момент отсутствует, однако для создания и использования экземпляров класса она пока и не нужна.
В некотором смысле объекты похожи на программные модули, для использования которых необходимо изучить лишь интерфейсную часть, раздел реализации для этого изучать не требуется. Поэтому дальше от описания класса мы перейдем не к реализации методов, а к созданию на их основе объектов.
Объекты
Чтобы от описания класса перейти к объекту, следует выполнить соответствующее объявление в секции var:
После создания объект можно использовать в программе: получать и устанавливать значения его полей, вызывать его методы. Доступ к полям и методам объекта происходит с помощью уточненных имен, например:
Кроме того, как и при работе с записями, допустимо использование оператора with, например:
Если объект становится ненужным, он должен быть удален вызовом специального метода Destroy, например:
Вызов деструктора для несуществующих объектов недопустим и при выполнении программы приведет к ошибке. Чтобы избавить программистов от лишних ошибок, в объекты ввели предопределенный метод Free, который следует вызывать вместо деструктора. Метод Free сам вызывает деструктор Destroy, но только в том случае, если значение объектной переменной не равно nil. Поэтому последнюю строчку в приведенном выше примере можно переписать следующим образом.
После уничтожения объекта переменная Reader сохраняет свое значение, продолжая ссылаться на место в памяти, где объекта уже нет. Если эту переменную предполагается еще использовать, то желательно присвоить ей значение nil, чтобы программа могла проверить, существует объект или нет. Таким образом, наиболее правильная последовательность действий при уничтожении объекта должна быть следующая:
С помощью стандартной процедуры FreeAndNil это можно сделать проще и элегантнее:
Значение одной объектной переменной можно присвоить другой. При этом объект не копируется в памяти, а вторая переменная просто связывается с тем же объектом, что и первая:
Объекты могут выступать в программе не только в качестве переменных, но также элементов массивов, полей записей, параметров процедур и функций. Кроме того, они могут служить полями других объектов. Во всех этих случаях программист фактически оперирует указателями на экземпляры объектов в динамической памяти. Следовательно, объекты изначально приспособлены для создания сложных динамических структур данных, таких как списки и деревья. Указатели на объекты для этого не нужны.
В некоторых случаях требуется, чтобы объекты разных классов содержали ссылки друг на друга. Возникает проблема: объявление первого класса будет содержать ссылку на еще не определенный класс. Она решается с помощью упреждающего объявления:
Первое объявление класса TDelimitedReader называется упреждающим (от англ. forward). Оно необходимо для того, чтобы компилятор нормально воспринял объявление поля Owner в классе TDelimitedReader.
Итак, вы уже имеете некоторое представление об объектах, перейдем теперь к вопросу реализации их методов.
Конструкторы и деструкторы
Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированных слов function и procedure используются слова constructor и destructor. Для нашего класса TDelimitedReader потребуется конструктор, которому в качестве параметра будет передаваться имя обрабатываемого файла и разделитель элементов:
Приведем их возможную реализацию:
Конструктор применяется к классу или к объекту. Если он применяется к классу,
то выполняется следующая последовательность действий:
Если конструктор применяется к объекту,
то конструктор выполняется как обычный метод. Другими словами, новый объект не создается, а происходит повторная инициализация полей существующего объекта. В этом случае конструктор не возвращает никакого значения. Далеко не все объекты корректно себя ведут при повторной инициализации, поскольку программисты редко закладывают такую возможность в свои классы. Поэтому на практике повторная инициализация применяется крайне редко.
Деструктор уничтожает объект, к которому применяется:
Как и обычные методы, деструктор может иметь параметры, но эта возможность используется редко.
Методы
Процедуры и функции, предназначенные для выполнения над объектами действий, называются методами. Предварительное объявление методов выполняется при описании класса в секции interface модуля, а их программный код записывается в секции implementation. Однако в отличие от обычных процедур и функций заголовки методов должны иметь уточненные имена, т.е. содержать наименование класса. Приведем возможную реализацию одного из методов в классе TDelimitedReader:
Обратите внимание, что внутри методов обращения к полям и другим методам выполняются как к обычным переменным и подпрограммам без уточнения экземпляра объекта. Такое упрощение достигается путем использования в пределах метода псевдопеременной Self (стандартный идентификатор). Физически Self представляет собой дополнительный неявный параметр, передаваемый в метод при вызове. Этот параметр и указывает экземпляр объекта, к которому данный метод применяется. Чтобы пояснить сказанное, перепишем метод SetActive, представив его в виде обычной процедуры:
Согласитесь, что метод SetActive выглядит лаконичнее процедуры TDelimitedReader_SetActive.
Практика показывает, что псевдопеременная Self редко используется в явном виде. Ее необходимо применять только тогда, когда при написании метода может возникнуть какая-либо двусмысленность для компилятора, например при использовании одинаковых имен и для локальных переменных, и для полей объекта.
Если выполнить метод SetActive,
то обрабатываемый файл будет открыт. При этом неявный параметр Self будет содержать значение переменной Reader. Такой вызов реализуется обычными средствами процедурного программирования приблизительно так:
Свойства
Понятие свойства
Объявление свойства выполняется с помощью зарезервированного слова property, например:
Обращение к свойствам выглядит в программе как обращение к полям:
Если один из спецификаторов доступа опущен, то значение свойства можно либо только читать (задан спецификатор read), либо только записывать (задан спецификатор write). В следующем примере объявлено свойство, значение которого можно только читать.
Здесь свойство ItemCount показывает количество элементов в массиве FItems. Поскольку оно определяется в результате чтения и разбора очередной строки файла, пользователю объекта разрешено лишь узнавать количество элементов.
В отличие от полей свойства не имеют адреса в памяти, поэтому к ним запрещено применять операцию @. Как следствие, их нельзя передавать в var— и out-параметрах процедур и функций.
Технология объектно-ориентированного программирования в среде Delphi предписывает избегать прямого обращения к полям, создавая вместо этого соответствующие свойства. Это упорядочивает работу с объектами, изолируя их данные от непосредственной модификации. В будущем внутренняя структура класса, которая иногда является достаточно сложной, может быть изменена с целью повышения эффективности работы программы. При этом потребуется переработать только методы чтения и записи значений свойств; внешний интерфейс класса не изменится.
Методы получения и установки значений свойств
Использование методов для получения и установки свойств позволяет проверить корректность значения свойства, сделать дополнительные вычисления, установить значения зависимых полей и т.д. Например, в методе SetActive вполне целесообразно осуществить проверку состояния файла (открыт или закрыт), чтобы избежать его повторного открытия или закрытия:
Наличие свойства Active позволяет нам отказаться от использования методов Open и Close, традиционных при работе с файлами. Согласитесь, что открывать и закрывать файл с помощью свойства Active гораздо удобнее и естественнее. Одновременно с этим свойство Active можно использовать и для проверки состояния файла (открыт или нет). Таким образом, для осуществления трех действий требуется всего лишь одно свойство! Это делает использование Ваших классов другими программистами более простым, поскольку им легче запомнить одно понятие Active, чем, например, три метода: Open, Close и IsOpen.
Значение свойства может не храниться, а вычисляться при каждом обращении к свойству. Примером является свойство ItemCount, значение которого вычисляется как Length(FItems).
Свойства-массивы
Элементы массива Items можно только читать, поскольку класс TDelimitedReader предназначен только для чтения данных из файла.
В описании свойства-массива разрешено использовать только методы, но не поля. В этом состоит отличие свойства-массива от обычного свойства.
Свойство-массив может быть многомерным. В этом случае методы чтения и записи элементов должны иметь столько же индексных параметров соответствующих типов, что и свойство-массив.
Свойства-массивы имеют два важных отличия от обычных массивов:
их индексы не ограничиваются диапазоном и могут иметь любой тип данных, а не только Integer. Например, можно создать свойство-массив, в котором индексами будут строки. Обращение к такому свойству могло бы выглядеть примерно так:
операции над свойством-массивом в целом запрещены; разрешены операции только с его элементами.
Свойство-массив как основное свойство объекта
Свойство-массив можно сделать основным свойством объектов данного класса. Для этого в описание свойства добавляется слово default:
Такое объявление свойства Items позволяет рассматривать сам объект класса TDelimitedReader как массив и опускать имя свойства-массива при обращении к нему из программы, например:
Следует помнить, что только свойства-массивы могут быть основными свойствами объектов; для обычных свойств это недопустимо.
Методы, обслуживающие несколько свойств
Один и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод чтения (записи) первым параметром.
В следующем примере уже известный Вам метод GetItem обслуживает три свойства: FirstName, LastName и Phone:
Обращения к свойствам FirstName, LastName и Phone заменяются компилятором на вызовы одного и того же метода GetItem, но с разными значениями параметра Index:
Обратите внимание, что метод GetItem обслуживает как свойство-массив Items, так и свойства FirstName, LastName и Phone. Удобно, не правда ли!
Перед тем, как перейти к более сложным понятиям ООП, приведем полную реализацию класса TDelimitedReader. Настоятельно рекомендуем Вам внимательно ознакомиться с этой реализацией, поскольку в ней сведено воедино все то, о чем говорилось в предыдущих разделах.