Урок 61 - Свойства классов Delphi, (часть 3/5)

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

   В реализации конструктора обычно первым идет вызов наследуемого конструктора с помощью ключевого слова inherited. В результате инициализируются все наследуемые поля. При этом порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. После вызова наследуемого конструктора в процедуре инициализируются новые поля, введенные в данном классе.

   Не обязательно при создании класса объявлять его конструктор. Если он не объявлен, то автоматически в момент создания объекта вызовется конструктор родительского класса. Мы это видели в примере класса TPerson и его тестового приложения, созданных во второй части. Там мы обошлись без конструктора, и все работало нормально. Но давайте несколько расширим возможности нашего класса, и посмотрим, сможем ли мы обойтись без конструктора. Добавим в класс открытые поля AgeMin, AgeMax, обозначающие минимальную и максимальную границы допустимого возраста. Подобные границы полезны, если приложение использует наш класс для регистрации претендентов на какую-то должность при приеме на работу, или для регистрации абитуриентов при приеме в учебное заведение. Пусть при задании года рождения класс автоматически проверяет, удовлетворяет ли регистрируемая личность поставленным возрастным ограничениям. Для реализации этой идеи в объявлении класса надо произвести следующие изменения:

uses   ...,   DateUtils;

type
	TPerson  = class
	private

	protected 
		procedure SetYear(Value: word); // Процедура записи

	public 
		AgeMin, AgeMax:  word;
		property Year: word read FYear write SetYear;
	end;

   В оператор uses вводится ссылка на модуль DateUtils, В этом модуле объявлены функции, которые мы будем использовать при записи года рождения. В защищенный раздел класса protected вводится объявление процедуры записи года рождения. В открытый раздел класса вводятся переменные AgeMin и AgeMax. И изменяется объявление свойства Year: теперь в него вводится ссылка на процедуру записи SetYear.
Реализация процедуры записи SetYear может быть следующей:

procedure TPerson.SetYear(Value:  word); //  Процедура записи года рождения 
var 
	NowYear: word;
begin
	NowYear:= YearOf(Date);
	if (NowYear - Value >= AgeMin) and (NowYear - Value <= AgeMax) then
		FYear:= Value else ShowMessage('Недопустимый год рождения '+IntToStr(Value)); 
 end;

   В переменную NowYear заносится текущий год. Делается это так. Вызывается функция Date, которая возвращает текущую дату. Затем к этому результату применяется функция YearOf, которая выделяет из даты год. Функция YearOf объявлена в модуле DateUtils. Именно из-за нее этот модуль подключается предложением uses.

   Остальное просто: проверяется, укладывается ли заданный год рождения Value в заданный диапазон, и если не укладывается, пользователю выдается соответствующее сообщение.

   Возможно, вы уже заметили, почему все это пока не будет нормально работать. Если нет, введите описанные изменения в ваш класс и выполните тестовое приложение. Вы увидите, что можете занести в объект класса только того, кто родился в текущем году. А такой гражданин, пожалуй, слишком молод для приема на работу или учебу. Объясняется это просто: целые поля, такие как AgeMin и AgeMax, инициализируются нулевыми значениями. А нам нужны какие-то другие, более разумные возрастные рамки. Конечно, пользователь класса легко может этот недостаток устранить. Достаточно ввести, например, в обработчик события формы OnCreate после оператора создания объекта класса операторы вида:

Pers.AgeMax := 45;
Pers.AgeMin := 16;

   Тем самым пользователь задает возрастные рамки для своего приложения. Ведь открытые поля AgeMin и AgeMax мы для того и вводили. Но хотелось бы, чтобы в случаях, если пользователю не требуется какой-то специфический возрастной диапазон, класс позволял бы работать с любыми реальными годами рождения. Но именно реальными, чтобы предотвратить случайную ошибку пользователя, когда он задаст, например, год рождения 3 или 3003.

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

constructor Create;

А в раздел implementation добавьте реализацию конструктора:

constructor TPerson.Create; 
const 
	Unknown = 'неизвестный'; 
begin
	inherited;
	FSex := #0;
	AgeMax := 150;
	FName := Unknown;
	FDepl := Unknown;
	FDep2 := Unknown;
	FDep3 := Unknown; 
end;

   Первый оператор вызывает с помощью ключевого слова inherited наследуемый конструктор родительского класса. А затем задаются начальные значения различных полей. Кроме задания максимального возраста 150 лет, тут исправляются еще некоторые недостатки нашего класса. Задается значение пола равное нулевому символу. По такому значению в дальнейшем можно проверять, был ли указан пол личности. И задаются строки «неизвестный» в качестве начального значения строковых нолей. Так что теперь, если какие-то данные о личности неизвестны, вместо них будет выдаваться этот текст, а не пустая строка. Думаю, что пользователю это будет удобно.

   Теперь рассмотрим деструкторы. Это специальные методы, уничтожающие объект и освобождающие занимаемую им память. Деструктор автоматически вызывается при выполнении метода Free объекта класса. Если в вашем классе деструктор не объявлен, вызывается деструктор родительского класса.

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

   Давайте введем в наш класс TPerson еще одно добавление, которое расширит его возможности. Создадим возможность хранить форматированный текст в формате .rtf, который может поступать, как вы знаете, из окна RichEdit. Но форматированный текст можно хранить или в объекте класса TRichEdit, или в файле, в который он записан методом SaveToFile свойства Lines окна RichEdit. Создать в нашем классе внутренний объект класса TRichEdit мы не можем. Так что остается вариант хранения во временном файле.
Добавьте в класс TPerson два поля:

FDoc: ^TRichEdit;
FileTMP: string;

   Поле FDoc будет служить указателем на внешний компонент класса TRichEdit, из которого будет загружаться форматированный текст в файл, и в который будет грузиться текст из файла. А в переменной FileTmp будем хранить имя временного файла. Чтобы компилятор принял объявление поля FDoc, он должен понять идентификатор TRichEdit. Этот класс объявлен в модуле ComCtrls. Так что добавьте ссылку на этот модуль в предложение uses.
Введите в открытый раздел класса объявления двух процедур:

procedure SetDoc(var Value: TRichEdit); procedure GetDoc(var Value: TRichEdit);

   Первая из них будет запоминать форматированный текст из окна RichEdit, переданного в нее как указатель на объект (вспомните смысл ключевого слова var). А вторая процедура будет заносить в окно, указанное аналогичным образом ее параметром, текст из файла. Реализация этих функций может иметь вид:

procedure TPerson.SetDoc(var Value: TRichEdit); 
begin
	if   FileTMP = ''
		then FileTMP:= FName +‘.tmp’;
	FDoc:= @Value;
	FDoc^.Lines.SaveToFile(FileTMP); 
end;

procedure TPerson.GetDoc(var Value: TRichEdit); 
begin 
	if FileTMP <> ''
		then FDoc^.Lines.LoadFromFile(FileTMP); 
end;

   В процедуре SetDoc сначала проверяется, задавалось ли уже имя временного файла. Если нет, то это имя формируется из строки FName (фамилия, имя, отчество) и расширения .tmp. Затем переменной FDoc задается адрес компонента TRichEdit. А дальнейшее понятно: форматированный текст записывается в файл с именем, хранящимся в переменной FileTMP.

   Добавьте в свое тестовое приложение окно RichEdit. Можете добавить также компонент FontDialog и обеспечить возможность форматирования текста в окне RichEdit. В обработчик щелчка на кнопке Запись вставьте оператор:

SetDoc(RichEditl);

А в обработчик щелчка на кнопке Чтение вставьте оператор:

GetDoc(RichEditl);

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

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

   В нашем случае в открытый раздел класса следует ввести объявление деструктора:

destructor Destroy; override;

Смысл ключевого слова override вы узнаете позднее. Пока просто напишите его, не задумываясь о его назначении. Реализация деструктора имеет вид:

destructor  TPerson.Destroy;	
begin
	if   FileTMP  <> ' ' then DeleteFile(FileTMP);
	inherited; 
end;
Если файл создавался, то он уничтожается. Введение такого деструктора никак не отразится на внешнем поведении вашего тестового приложения. Но теперь вы не оставляете на диске временный и уже ненужный файл.

   Следующий урок будет посвящён методам, наследованиям классов, операциям с классами.

Удачи!
Встретимся в следующем уроке!




Добавил(а): OOP_OOP Дата: 2016-10-02
Замечательные уроки для новичков 59(классы)... 61 (свойства классов) воспроизводяться почти до конца. Но почти... Было бы очень желательно (для новичков опять же) привести законченый лучше зазипований проект в DElphi 7 на уровне информации 61 урока, что бы можно было сравнить работающий проект автора урока с проектом и процедурами собраными новичком на основе информации уроков 59-61 и найти ошибку. Заранее благодарен.


    No results found.
Отменить.