Программа «Светофор» как объект. Часть 1.

Delphi. Конструктор класса.

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

Теперь постараемся оформить «светофор» как единое целое — в виде объекта. Этот объект можно будет хранить в отдельном модуле. Для использования объекта потребуется минимальное количество средств в основной программе.

Разместим объект «светофор» в отдельном невизуальном модуле типа «Unit» и назовём его «Svetofor».

Для имитации работы светофора нам понадобятся два элемента тира «Panel», семь элементов типа «Shape», один элемент типа «Image» и один элемент типа «ListBox». Также понадобится четыре невизуальных компонента «Timer» для управления фонарями светофора.

Сам светофор будет реализован в виде класса, унаследованного от «TwinControl», так как будут использованы визуальные компоненты.

Создадим новый проект. В меню «File» выберем «New» ► «Unit» и сохраним созданный модуль под именем «Svetofor».

Потом скопируем предложение «Uses» из модуля «Unit1» в модуль «Svetofor», так как нам понадобятся классы визуальных компонент.

Построение класса «TSvetofor»

Так как создаваемый нами класс должен быть виден из другого модуля, то разместим его в секции «Interface», записав:

unit Svetofor;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TSvetofor=class(TWinControl)
public
private
end;
implementation
end.

Чтобы модуль «Svetofor» мог быть виден из «Unit1», используем в «Unit1» ссылку: «File» ► «Use Unit …» ► «Svetofor», после чего в «Unit1» после «implementation» добавляется предложение:

implementation
uses Svetofor;

Как говорилось ранее, для создания объекта «Svetofor» из главной страницы Unit1 надо вызвать конструктор класса — «create«:

vSvetofor:=TSvetofor.create(self);

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

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

Нам же для правильного отображения объекта на экране необходимо в описании класса задать собственный конструктор. Так как конструктор обязан быть виден из другого модуля, размещаем его описание в секции «public»:

TSvetofor=class(TWinControl)
public
constructor create(tc:TComponent);
private
end;

Заготовка текста конструктора (размещаемая в секции «implementation» модуля) будет иметь вид:

constructor TSvetofor.create(tc:TComponent);
begin
inherited create(tc);

end;

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

Предварительные замечания.

Сделаем несколько предварительных замечаний.

  1. Чтобы создать объект какого-либо класса, вызывается конструктор, определённый в этом классе. В процедуре или в обработчике события записывают:


var MyObject:TMyObject;
implementation
MyObject:=TMyObject.Create(владелец_создаваемого_объекта);

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

2. Указатель на первый байт созданного экземпляра объекта будет записан в переменную «MyObject«.
Если MyObject — глобальная переменная, то она будет храниться в сегменте данных программы.
Если это локальная переменная — то в стеке.
При этом надо помнить, что при выходе из процедуры связь с локальными переменными прерывается. В то же время память, выделенная в куче под экземпляр объекта, остаётся занятой и недоступной для дальнейшего использования.

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

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

3. Если переменная объектного типа глобальная, то при её объявлении она автоматически «обнуляется» путём присвоения ей предопределённого значения «nil».
Если переменная локальная, то правильным тоном программирования является «ручное» присвоение переменной этого значения: MyObject:=nil;.
Это избавит от возможных потенциальных ошибок, которые трудно диагностировать даже в режиме отладки.

4. Если переменная глобальная, то после проведения уничтожения экземпляра объекта память освобождается, но в самой переменной остается записанный ранее в неё указатель. Только теперь он «указывает» неизвестно на что и обращение к переменной вызовет фатальную ошибку. Поэтому после уничтожения переменной надо присваивать этой переменной значение «nil» вручную.

Создание пользовательского класса.

Каждый пользовательский класс конструируется на основе какого-либо существующего в Delphi класса. Самый простой вариант — создать класс на основе базового класса Tobject. Это самый первый класс в иерархии классов, их «родоначальник». Объявляя свой класс:

Type
TMyClass=class(TObject)
public
constructor create;

private

end;

Сделав такое объявление, мы тем самым говорим, что «TObject» — родительский класс, а «TMyClass» — дочерний.

Таким образом, при создании класса сначала формируются все поля, методы и свойства класса-родителя (TObject), а затем в «хвост» дописываются те поля и методы, которые формирует пользователь (расположены на месте многоточий). Аналогией может служить прицепление в конец состава дополнительного вагона.

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

Например, объявим тип TMyObject, родительским для которого будет класс TwinControl:

Если объявить переменную
var MyObject:TWinControl;

и создать объект MyObject:=TMyObject.Create(tc:Tcomponent),

то после создания объекта становятся доступными все методы, поля и свойства как класса «TWinControl», так и добавленные пользователем.

Замечание. Ссылкой на объект-владелец «tc» конструктор воспользуется, если полями создаваемого объекта являются, в свою очередь, другие объекты.
«Владелец» отвечает за уничтожение принадлежащих ему объектов и уничтожает их в момент уничтожения самого «владельца». Таким образом достигается гарантированная зачистка памяти.
Владельца могут иметь только объекты, начиная с класса «Tcomponent». До этого класса конструктор «Create» вызывается без параметра. Например,

MyObject:=TPersistent.Create;


Ссылка на родительский объект.

Рассмотренная структура объекта позволяет осуществлять «неочевидные» присваивания, необходимость которых бывает очень полезна в некоторых случаях.

Рассмотрим сначала «универсальное» присваивание:

var MyObject:TObject;
MyObject:=любой_класс.Create(владелец);

В чистом виде через переменную MyObject можно будет обратиться только к полям и методам класса Tobject. Но потенциально можно обращаться к полям и методам класса «любой_класс». Для этого надо привести переменную MyObject к классу, от которого создан объект.
Это делается или с помощью оператора «as», или «явным» образом:

( MyObject as «любой_класс») или «любой_класс»( MyObject)

Теперь можно обратиться к полям и методам класса «любой_класс»:

(MyObject as «любой_класс»).«метод_класса» или «любой_класс»( MyObject).поле

Как мы увидим в дальнейшем, этот приём может быть весьма полезен.

Унаследованный конструктор.

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

constructor TSvetofor.create(tc:TComponent);
begin
inherited create(tc);
end;

Для чего нужна эта директива? При вызове конструктора самым первым делом производится выделение памяти. Сначала компилятор определяет объём памяти родительского класса. После этого присоединяет к нему объём, необходимый для размещения полей, методов и свойств, добавленных пользователем.
Наконец, размещает объект в «куче» и возвращает ссылку на него, присваивая эту ссылку переменной:

var Svetofor:TSvetofor;

Svetofor:=TSvetofor.Create(form1);

В нашем случае мы определяем, что «владельцем» является форма. Закрытие формы приводит к удаления объекта «Svetofor» из памяти.

Продолжение построения класса «Tsvetofor» в следующей статье.

***

Обновлено: 28.05.2021 — 20:25

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *