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

Объектно-ориентированное программирование в Pascal: классы и объекты Полезное

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

Важная оговорка: ООП живет не в «чистом» Pascal, а в его расширении — Object Pascal. Именно на нем работают Free Pascal (компилятор fpc) и Delphi. Pascal — учебный язык, поэтому синтаксис ниже удобно проверять во Free Pascal 3.2.2 с директивой {$mode objfpc}.

Разберем: чем object отличается от class, как устроены поля и методы, конструкторы и деструкторы, инкапсуляция, наследование и полиморфизм через virtual/override.

Две разных сущности: object и class

В Object Pascal есть два ключевых слова для объектных типов, и это не синонимы:

  • object — старая модель из Turbo/Borland Pascal. Это значимый (value) тип: переменная хранит сам объект, обычно на стеке, память освобождается автоматически.
  • class — современная модель из Delphi и Free Pascal. Это ссылочный (reference) тип: переменная хранит ссылку на объект в куче, который надо создать конструктором и освободить вручную.

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

Признак object (старый) class (современный)
Происхождение Turbo/Borland Pascal Delphi, Free Pascal
Модель памяти значимый тип, обычно на стеке ссылочный тип, в куче
Создание не требуется (или New) Create (конструктор)
Удаление автоматически (или Dispose) Free вручную
Переменная хранит сам объект ссылку (nil до Create)
Наследование есть есть
Виртуальные методы virtual + constructor virtual + override
Свойства (property) ограниченно полноценно
Когда брать поддержка старого кода новый код, GUI, Lazarus

Далее основные примеры — на class, как в актуальном коде. Отдельно покажу object.

Поля, методы и минимальный класс

Начнем с законченного примера, который компилируется и печатает результат:

program ClassDemo;

{$mode objfpc}{$H+}

type
  TCounter = class
  private
    FValue: Integer;                     // скрытое поле
  public
    procedure Inc;
    property Value: Integer read FValue;  // доступ только на чтение
  end;

procedure TCounter.Inc;
begin
  FValue := FValue + 1;
end;

var
  c: TCounter;
begin
  c := TCounter.Create;   // создаем экземпляр в куче
  c.Inc;
  c.Inc;
  WriteLn(c.Value);       // 2
  c.Free;                 // освобождаем память
end.

Здесь FValue — поле (данные), Inc — метод (подпрограмма над полями). Тело метода пишется отдельно, а его заголовок повторяет описание с префиксом-именем класса: TCounter.Inc. Внутри метода к полю обращаемся просто по имени, без указания экземпляра.

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

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

Смысл прикладной: пока класс меняет значение только своими методами, никакой чужой код не испортит его состояние случайно. Пометка про упрощение: в Free Pascal private закрывает поле на уровне модуля (внутри того же файла его видно), а строгое сокрытие на уровне класса дает strict private.

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

Наследование — это создание нового класса на основе существующего: потомок получает поля и методы предка и может добавить свои. В Object Pascal предок указывается в скобках: TDog = class(TAnimal). Для обращения к реализации предка есть слово inherited.

Полиморфизм: virtual и override

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

program Shapes;

{$mode objfpc}{$H+}

type
  TShape = class
  protected
    FName: string;
  public
    constructor Create(const AName: string);
    function Area: Double; virtual;      // виртуальный метод
    procedure Show;
  end;

  TCircle = class(TShape)
  private
    FR: Double;
  public
    constructor Create(AR: Double);
    function Area: Double; override;     // перекрываем
  end;

constructor TShape.Create(const AName: string);
begin
  FName := AName;
end;

function TShape.Area: Double;
begin
  Result := 0;
end;

procedure TShape.Show;
begin
  WriteLn(FName, ': площадь = ', Area:0:2);
end;

constructor TCircle.Create(AR: Double);
begin
  inherited Create('круг');   // вызов конструктора предка
  FR := AR;
end;

function TCircle.Area: Double;
begin
  Result := Pi * FR * FR;
end;

var
  s: TShape;
begin
  s := TCircle.Create(2);
  s.Show;      // круг: площадь = 12.57
  s.Free;
end.

Переменная s объявлена как TShape, но реально указывает на TCircle. Метод Show вызывает Area, а из-за virtual/override выполняется версия из TCircle — получаем 12.57 (площадь круга радиуса 2). Если убрать virtual и override, вызовется TShape.Area и результат станет 0.00 — это статическое связывание, полиморфизма нет.

Конструкторы и деструкторы

Конструктор (constructor Create) выделяет память под объект и инициализирует поля, деструктор (destructor Destroy) освобождает ресурсы. На практике объект уничтожают вызовом Free — он проверяет, что ссылка не nil, и вызывает Destroy. Правило простое: у каждого Create в вашем коде должен быть парный Free, иначе память утечет.

Частая ошибка новичка — использовать переменную-класс без создания объекта:

var
  c: TCircle;
begin
  c.Show;   // c = nil, объект не создан
end.

Фактический результат при запуске:

An unhandled exception occurred at $...:
EAccessViolation: Access violation
Runtime error 216

Исправление — создать объект до обращения и освободить после:

var
  c: TCircle;
begin
  c := TCircle.Create(2);
  c.Show;
  c.Free;
end.

Старая модель: object

Тип object не требует Create/Free, если размещается на стеке. Он до сих пор компилируется во Free Pascal и встречается в старом коде:

program OldObject;

{$mode objfpc}

type
  TPoint = object          // значимый тип
    X, Y: Integer;
    procedure Init(AX, AY: Integer);
    function Sum: Integer;
  end;

procedure TPoint.Init(AX, AY: Integer);
begin
  X := AX;
  Y := AY;
end;

function TPoint.Sum: Integer;
begin
  Result := X + Y;
end;

var
  p: TPoint;               // сам объект, на стеке
begin
  p.Init(3, 4);
  WriteLn(p.Sum);          // 7
end.

Здесь p — это сам объект, а не ссылка. Память под него не выделяют и не освобождают вручную. Для нового кода все же берут class: у него полноценные свойства, единая модель с Delphi и Lazarus, привычные virtual/override.

Выводы

  • ООП в Pascal доступно через Object Pascal (Free Pascal, Delphi), а не в «чистом» ISO Pascal.
  • class — ссылочный тип в куче (нужны Create и Free); object — значимый тип на стеке (память автоматическая). Для нового кода берут class.
  • Инкапсуляция закрывает поля через private и открывает их через методы и property.
  • Наследование задается через class(Предок), а к предку обращаются словом inherited.
  • Полиморфизм работает на паре virtual в предке и override в потомке; без них вызов статический.

Где применяется / связь с практикой

Pascal остается учебным языком: на нем в школах и на первых курсах разбирают алгоритмы, структуры данных и основы ООП, а понимание классов и наследования потом переносится на Java, C# или C++. Object Pascal используют и на практике — десктопные приложения на Delphi и Lazarus строятся именно из классов.

Освойте тему на практике

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

FAQ

Чем object и class отличаются от record?
record хранит только поля (в новых версиях Object Pascal допускает и методы), но не поддерживает наследование и виртуальные методы. object и class добавляют наследование и полиморфизм.

Нужно ли вызывать Free для переменной типа object?
Нет, если object размещен на стеке — память освобождается автоматически. Ручное Free/Dispose нужно для class и для object, созданного через New.

Можно ли писать ООП в стандартном Pascal без расширений?
Нет. Классы и объекты — часть Object Pascal; во Free Pascal их включают режимы {$mode objfpc} или {$mode delphi}. В чистом ISO Pascal их нет.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)