В структурных приложениях данные обычно хранятся в записях, а логика приложения распределяется между многочисленными функциями и процедурами. Данные всякого приложения и код, использующий эти данные, всегда остаются разделенными.
Поясним это на примере. Далее показан стандартный способ работы с данными в структурных приложениях.
Листинг 10.1. Работа с записями
01.program Project1;02.{$APPTYPE CONSOLE}03.uses04.SysUtils;05.type06.TAnimal = record07.Name: string;08.Age: Integer;09.Hungry: Boolean;10.end;11.procedure ShowInfo(var AAnimal: TAnimal);12.const13.HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes');14.begin15.with AAnimal do16.begin17.WriteLn('Name: ', Name);18.WriteLn('Age: ', Age);19.WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);20.end; // with AAnimal21.end;22.var23.MyDog: TAnimal;24.begin25.MyDog.Name := 'Apollo';26.MyDog.Age := 10;27.MyDog.Hungry := False;28.ShowInfo(MyDog);29.ReadLn;30.end.
Чтобы получить доступ к данным, хранящимся в записи MyDog, вы должны передать эту запись процедуре в качестве параметра-переменной. Каждая процедура, которой нужны данные, хранящиеся в записи типа TAnimal, должна иметь параметр, который будет принимать указатель (параметры-переменные действуют как указатели) на фактические данные.
Такой подход порождает три нежелательных побочных эффекта: во-первых, процедуре или функции нужно всегда передавать указатель на запись; во-вторых, необходимо подготовить код, который будет проверять достоверность данных; в-третьих, нужно проверять, существуют ли эти данные на самом деле.
Если иметь дело только со статическими записями, то, разумеется, в этом случае данные будут существовать всегда. Однако если возникнет необходимость в использовании динамических записей, то тогда потребуется проверять, действительно ли эти записи существуют, поскольку попытка доступа к полям в несуществующем значении обязательно приведет к возникновению ошибки.
Чтобы надлежащим образом использовать динамическую запись, необходимо изменить код в процедуре так, чтобы можно было принимать реальный указатель на запись и, вдобавок к этому, проверять, ссылается ли указатель на действительную ячейку в памяти. Все эти условия соблюдены в следующем коде.
Листинг 10.2. Доступ к записям с помощью указателей
01.program Project1;02.{$APPTYPE CONSOLE}03.uses04.SysUtils;05.type06.PAnimal = ^TAnimal; { pointer to a TAnimal record }07.TAnimal = record08.Name: string;09.Age: Integer;10.Hungry: Boolean;11.end;12.procedure ShowInfo(AAnimal: PAnimal);13.const14.HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes');15.begin16.if AAnimal = nil then Exit; 17.with AAnimal^ do18.begin19.WriteLn('Name: ', Name);20.WriteLn('Age: ', Age);21.WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);22.end; // with AAnimal23.end;24.var25.MyDog: TAnimal;26.MyDynamicCat: PAnimal;27.begin28.MyDog.Name := 'Apollo';29.MyDog.Age := 10;30.MyDog.Hungry := False;31.ShowInfo(@MyDog);32.New(MyDynamicCat);33.ShowInfo(MyDynamicCat);34.Dispose(MyDynamicCat);35.ReadLn;36.end.
Ключ к решению обозначенных трех проблем лежит в ООП. и особенно — в инкапсуляции. Чтобы не зависеть от указателей на действительные данные и не проверять достоверность этих данных, можно инкапсулировать (проще говоря, "вложить") запись TAnimal и процедуру Showlnfo в класс TAnimal.
Прежде чем продолжить разговор далее, следует обратить внимание на употребление термина "класс". В ООП классы представляют собой модели объектов, подобно тому, как в геноме человека заложена модель человеческого организма. Объект представляет собой экземпляр класса, или реальный продукт моделирования. Если бы этих моделей не было, природные механизмы не позволили бы нам продолжать свой род. а компилятор Delphi не смог бы создавать экземпляры объектов.
Создать базовый класс можно так же просто, как и базовую запись. Единственное отличие заключается в том. что для создания класса необходимо использовать зарезервированное слово class.
1.type2.TAnimal = class3.Name: string;4.Age: Integer;5.Hungry: Boolean; end;
Теперь, когда в нашем распоряжении имеется класс TAnimal. мы можем создать объект типа TAnimal и использовать его в своем приложении. Первое, что нужно сделать для создания объекта, это объявить переменную объекта, то есть объявить переменную, имеющую тип класса:
1.var2.MyDog: TAnimal;
Однако, это всего лишь объявление объекта. Чтобы объект MyDog можно было на самом деле использовать в приложении, мы должны его создать.
Объекты создаются с помощью конструктора — специального метода, называемого Create, который используется для создания экземпляров объектов. Синтаксическая структура создания объекта выглядит следующим образом:
1.Объект := Класс.Create;
Чтобы создать объект MyDog, мы должны вызвать конструктор TAnimal, поскольку переменная MyDog объявлена как TAnimal:
1.MyDog := TAnimal.Create;
Конструктор выделяет участок памяти в области динамически распределяемой памяти (куче), необходимый для хранения объявленных полей, и оставляет все значения полей пустыми. Кроме этого, конструктор возвращает ссылку на новый объект. После вызова конструктора переменная MyDog будет указывать на новый объект, созданный с помощью конструктора. Понятно, что объекты на самом деле являются указателями, однако вам не нужно будет вновь ссылаться на объекты (то есть не нужно будет писать символ Л), чтобы обратиться к их полям или методам.
1.MyDog.Name := 'Apollo'; WriteLn(MyDog.Name) ;
Поскольку объекты создаются в куче динамическим образом, то сразу же после завершения работы с ними вам нужно будет освобождать занимаемую этими объектами память. Процесс освобождения памяти, занимаемой объектом, называется уничтожением объекта.
Чтобы уничтожить объект, вы должны вызвать деструктор объекта. Деструктором называется специальный метод Destroy, который используется для освобождения памяти от объекта. Вызывать метод Destroy напрямую не следует ни при каких обстоятельствах. Взамен необходимо вызывать метод Free, поскольку он, прежде чем уничтожать объект, проверяет, существует ли он. Несуществующий объект ссылается на недействительную ячейку памяти, поэтому обращение к такой ячейке приведет к возникновению ошибки.
При вызове методов Destroy или Free необходимо ссылаться на объект, а не на его класс:
1.MyDog.Destroy; MyDog.Free;
На примере кода в листинге 10.3 показано правильное использование объекта.
Листинг 10.3. Использование объекта
01.program Project1;02.{$APPTYPE CONSOLE}03.uses04.SysUtils;05.type06.TAnimal = class07.Name: string;08.Age: Integer;09.Hungry: Boolean;10.end;11.var12.MyDog: TAnimal;13.begin14.{ create the object }15.MyDog := TAnimal.Create;16.{ use it }17.MyDog.Name := 'Apollo';18.WriteLn(MyDog.Name);19.{ destroy it }20.MyDog.Free;21.ReadLn;22.end.
Для полного завершения процесса инкапсуляции мы должны инкапсулировать процедуру Showlnfo. Инкапсуляция процедуры Showlnfo означает, что она будет объявлена как часть класса TAnimal:
1.type2.TAnimal = class Name: string; Age: Integer; Hungry: Boolean; procedure Showlnfo;3.end;
Имейте в виду, что это всего лишь объявление процедуры Showlnfo. Нам еще нужно написать реализацию метода. В Delphiреализация метода должна находиться за пределами блока класса (в отличие от С++, где допускается как встраиваемая, так и внешняя реализация метода).
При написании реализации метода (процедур и функций, принадлежащих классу) необходимо указывать, какому классу принадлежит метод. Поэтому, перед именем метода следует написать имя класса, разделяя их имена точкой.
1.procedure Класс.Метод(Список_Параметров); function Класс.Метод(Список_Параметров);
Следовательно, реализация метода Showlnfo будет выглядеть следующим образом:
1.procedure TAnimal.Showlnfo;2.begin3.end;
Метод Showlnfo не имеет списка параметров, поскольку все необходимые ему данные инкапсулированы в классе. Далее показан пример окончательной реализации метода Showlnfo:
1.procedure TAnimal.Showlnfo* const2.HUNGRY_STRING: array[Boolean] of string = ('No1, 'Yes'); begin3.WriteLn('Name : ', Name); WriteLn(1 Age: 1, Age);4.WriteLn(1 Hungry : 1, HUNGRY_STRING[Hungry]); end;
Поскольку поля и сам метод Showlnfo принадлежат одному и тому же классу, этот метод может обращаться к полям напрямую. В действительности, метод может обращаться к полям посредством невидимого указателя, называемого Self. КомпиляторDelphi неявным образом создает указатель Self для каждого объекта. Этот указатель ссылается на объект, в котором производится вызов метода; это значит, что с помощью указателя Self объект может ссылаться на самого себя (что следует из самого названия указателя: существительное self в английском языке употребляют, когда имеют в виду себя лично — прим. перев.). При желании, вы можете использовать указатель Self в методе Showlnfo для явной ссылки на поля класса TAnimal:
1.procedure TAnimal.Showlnfo; const2.HUNGRY_STRING: array[Boolean] of string - ('No', 'Yes'); begin3.WriteLn('Name: ', Self.Name); WriteLn('Age: \ Self.Age);4.WriteLn(1 Hungry : 1, HUNGRY_STRING[Self.Hungry]); end;
Популярность ООП в наши дни объясняется тем. что использование объектов в коде позволяет сделать его более ясным и простым для понимания. Поэтому сейчас вызов метода Showlnfo можно понять проще и быстрее, чем вызов процедуры Showl n f о, поскольку мы не должны заботиться об излишней передаче параметров.
Листинг 10.4. Полный код класса
01.program Project1;02.{$APPTYPE CONSOLE}03.uses04.SysUtils;05.type06.TAnimal = class07.Name: string;08.Age: Integer;09.Hungry: Boolean;10.procedure ShowInfo;11.end;12.procedure TAnimal.ShowInfo;13.const14.HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes');15.begin16.WriteLn('Name: ', Name);17.WriteLn('Age: ', Age);18.WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);19.end;20.var21.MyDog: TAnimal;22.begin23.{ create the object }24.MyDog := TAnimal.Create;25.{ use it }26.MyDog.Name := 'Apollo';27.MyDog.Age := 10;28.MyDog.Hungry := True;29.MyDog.ShowInfo;30.{ destroy it }31.MyDog.Free;32.ReadLn;33.end.
С инкапсуляцией записи TAnimal и процедуры Showlnfo мы покончили, однако умудрились сами себе оказать "медвежью услугу''. Мы создали класс в главном файле проекта, а классы должны всегда оставаться в отдельных модулях.
Чтобы переместить класс в новый модуль, потребуется предпринять следующие действия:
- Создать новый модуль (с именем Animal).
- Переместить объявление класса в раздел interface (раздел интерфейса) модуля.
- Переместить реализацию (или реализации) метода в раздел implementation (раздел реализации) модуля.
В конечном счете модуль Animal. pas должен выглядеть примерно так, как показано в листинге 10.5.
Листинг 10.5. Модуль класса TAnimal
01.unit Animal;02.interface03.type04.TAnimal = class05.Name: string;06.Age: Integer;07.Hungry: Boolean;08.procedure ShowInfo;09.end;10.implementation11.procedure TAnimal.ShowInfo;12.const13.HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes');14.begin15.WriteLn('Name: ', Name);16.WriteLn('Age: ', Age);17.WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);18.end;19.end.
Классы, подобно животным и растениям из реального мира, обладают способностью наследования свойств и методов от своих предшественников, или родителей. Однако классы не просто могут наследовать функциональные особенности родительского класса — они делают это всегда. Если вы случайно забудете указать родительский класс в объявлении класса, то Delphiнеявным образом породит его от первоначального родителя — класса TObject. Чтобы явным образом описать родительский класс, вы должны указать имя класса после зарезервированного слова class:
1.ТНовый_Класс - class (гТРодительский_Класс)
Например, используемый ранее класс TAnimal неявным образом порожден от класса TObject. При желании вы можете также явным образом описать родительский класс класса TAnimal:
1.type2.TAnimal = class(TObject) Name: string; Age: Integer; Hungry: Boolean;3.end;
Единственная причина, по которой мы можем реализовать экземпляры объектов типа TAnimal. состоит в том. что класс TAnimal наследует необходимые методы от класса TObject. Класс TObject инкапсулирует важные методы, необходимые для создания и уничтожения объектов, такие как Create, Destroy и Free.
Чтобы посмотреть весь список методов, унаследованных от класса TObject (или любого другого родительского класса), можете воспользоваться инструментом Code Insight (Анализатор кода) окна Code Editor (Редактор кода) (рис. 10.1).
Рис. 10.1. Методы, унаследованные от класса TObject

Чтобы отобразить список свойств и методов, принадлежащих объекту, введите имя объекта, поставьте точку и нажмите комбинацию клавиш <Сгг1+Пробел>. Класс TAnimal тоже может использоваться как родительский класс для других классов. Например, он может выступать в качестве родителя для классов TBird и TFish.
01.type02.TAnimal = class03.Name: string;04.Age: Integer;05.Hungry: Boolean;06.procedure Showlnfo; end;07.TFish = class(TAnimal)08.Carnivorous: Boolean;09.Length: Integer; end;10.TBird = class(TAnimal)11.CanFly: Boolean;12.end;
Оба класса. TFish и TBird. не только наследуют функциональные особенности класса TAnimal, но и расширяют возможности исходного класса за счет новых свойств, таких как Length и CanFly:
1.var2.F: TFish; begin3.F := TFish.Create;4.F.Name := 'Rainbow Trout';5.F.Length := 54;6.F.ShowInfo;7.F.Free; end
01.program Projectl;02.($APPTYPE CONSOLE}03.uses04.SysUtils,05.Animal in 'Animal.pas 1;06.var07.F: TFish;08.A: TAnimal;09.B: TBird;10.begin11.F := TFish.Create; A := TAnimal.Create; В := TBird.Create;12.SetAnimalName(F, 1 Fish1); SetAnimalName(A, 'Animal1); SetAnimalName(B, 'Bird');13.A.Free; B.Free; F.Free; end.