Програмування на мові Delphi. Глава 3. Об'єктно-орієнтоване програмування (ООП). Частина 3, Різне, Програмування, статті

попередня стаття серії


Зміст



Класи в програмних модулях


Класи дуже зручно збирати в модулі. При цьому їх опис поміщається в секцію interface, А код методів – в секцію implementation. Створюючи модулі класів, потрібно дотримуватися наступних правил:


Зберемо розглянуті раніше класи TTextReader, TDelimitedReader і TFixedReader в окремий модуль ReadersUnit:

unit ReadersUnit;

interface

type
TTextReader = class
private
/ / Поля
FFile: TextFile;
FItems: array of string;
FActive: Boolean;
/ / Методи
procedure PutItem(Index: Integer; const Item: string);
/ / Методи читання і запису властивостей
procedure SetActive(const AActive: Boolean);
function GetItemCount: Integer;
function GetEndOfFile: Boolean;
protected
/ / Методи читання і запису властивостей
function GetItem(Index: Integer): string;
/ / Абстрактні методи
function ParseLine (const Line: string): Integer; virtual; abstract;
public
/ / Конструктори і деструктори
constructor Create(const FileName: string);
destructor Destroy; override;
/ / Методи
function NextLine: Boolean;
/ / Властивості
property Active: Boolean read FActive write SetActive;
property Items[Index: Integer]: string read GetItem; default;
property ItemCount: Integer read GetItemCount;
property EndOfFile: Boolean read GetEndOfFile;
end;

TDelimitedReader = class(TTextReader)
private
/ / Поля
FDelimiter: Char;
protected
/ / Методи
function ParseLine(const Line: string): Integer; override;
public
/ / Конструктори і деструктори
constructor Create (const FileName: string; const ADelimiter: Char =;);
/ / Властивості
property Delimiter: Char read FDelimiter;
end;

TFixedReader = class(TTextReader)
private
/ / Поля
FItemWidths: array of Integer;
protected
/ / Методи
function ParseLine(const Line: string): Integer; override;
public
/ / Конструктори і деструктори
constructor Create(const FileName: string;
const AItemWidths: array of Integer);
end;

TMyReader = class(TDelimitedReader)
property FirstName: string index 0 read GetItem;
property LastName: string index 1 read GetItem;
property Phone: string index 2 read GetItem;
end;

implementation

{ TTextReader }

constructor TTextReader.Create(const FileName: string);
begin
inherited Create;
AssignFile(FFile, FileName);
FActive := False;
end;

destructor TTextReader.Destroy;
begin
Active := False;
inherited;
end;

function TTextReader.GetEndOfFile: Boolean;
begin
Result := Eof(FFile);
end;

function TTextReader.GetItem(Index: Integer): string;
begin
Result := FItems[Index];
end;

function TTextReader.GetItemCount: Integer;
begin
Result := Length(FItems);
end;

function TTextReader.NextLine: Boolean;
var
S: string;
N: Integer;
begin
Result := not EndOfFile;
if Result then / / Якщо не досягнуто кінець файлу
begin
Readln (FFile, S); / / Читання черговий рядки з файлу
N: = ParseLine (S); / / Розбір ліченої рядки
if N <> ItemCount then
SetLength (FItems, N); / / Відсікання масиву (якщо необхідно)
end;
end;

procedure TTextReader.PutItem (Index: Integer; const Item: string);
begin
if Index> High (FItems) then / / Якщо індекс виходить за межі масиву,
SetLength (FItems, Index + 1); / / то збільшення розміру масиву
FItems [Index]: = Item; / / Установка відповідного елемента
end;

procedure TTextReader.SetActive(const AActive: Boolean);
begin
if Active <> AActive then / / Якщо стан зміниться
begin
if AActive then
Reset (FFile) / / Відкриття файлу
else
CloseFile (FFile); / / Закриття файлу
FActive: = AActive; / / Збереження стану в поле
end;
end;

{ TDelimitedReader }

constructor TDelimitedReader.Create(const FileName: string;
const ADelimiter: Char = ;);
begin
inherited Create(FileName);
FDelimiter := ADelimiter;
end;

function TDelimitedReader.ParseLine (const Line: string): Integer;
var
S: string;
P: Integer;
begin
S := Line;
Result := 0;
repeat
P: = Pos (Delimiter, S); / / Пошук роздільника
if P = 0 then / / Якщо роздільник не знайдений, то вважається, що
P: = Length (S) + 1; / / роздільник знаходиться за останнім символом
PutItem (Result, Copy (S, 1, P – 1)); / / Установка елемента
Delete (S, 1, P); / / Видалення елемента з рядка
Result: = Result + 1; / / Перехід до наступного елемента
until S =; / / Поки в рядку є символи
end;

{ TFixedReader }

constructor TFixedReader.Create(const FileName: string;
const AItemWidths: array of Integer);
var
I: Integer;
begin
inherited Create(FileName);
/ / Копіювання AItemWidths в FItemWidths
SetLength(FItemWidths, Length(AItemWidths));
for I := 0 to High(AItemWidths) do
FItemWidths[I] := AItemWidths[I];
end;

function TFixedReader.ParseLine(const Line: string): Integer;
var
I, P: Integer;
begin
P := 1;
for I := 0 to High(FItemWidths) do
begin
PutItem (I, Copy (Line, P, FItemWidths [I])); / / Установка елемента
P: = P + FItemWidths [I]; / / Перехід до наступного елемента
end;
Result: = Length (FItemWidths); / / Кількість елементів постійно
end;

end.


Як можна помітити, в описі класів присутні нові ключові слова private, protected і public. З їх допомогою регулюється видимість частин класу для інших модулів і основної програми. Призначення кожного ключового слова пояснюється нижче.

Розмежування доступу до атрибутів об'єктів


Програміст може розмежувати доступ до атрибутів своїх об'єктів для інших програмістів (і себе самого) за допомогою спеціальних ключових слів: private, protected, public, published (Останнє не використовується в модулі ReadersUnit).



Перераховані секції можуть чергуватися в оголошенні класу в довільному порядку, проте в межах секції спочатку йде опис полів, а потім методів і властивостей. Якщо у визначенні класу немає ключових слів private, protected, public і published, То для звичайних класів всім полям, методам і властивостям приписується атрибут видимості public, А для тих класів, які породжені від класів бібліотеки VCL, – атрибут видимості published.

Всередині модуля ніякі обмеження на доступ до атрибутів класів, реалізованих в цьому ж модулі, не діють. До речі, це відрізняється від угод, прийнятих в деяких інших мовах програмування, зокрема в мові C + +.

Покажчики на методи об'єктів


У мові Delphi існують процедурні типи даних для методів об'єктів. Зовні оголошення процедурного типу для методу відрізняється від звичайного словосполученням of object, Записаним після прототипу процедури або функції:

type
TReadLineEvent = procedure (Reader: TTextReader; const Line: string) of object;


Мінлива такого типу називається покажчиком на метод (Method pointer). Вона займає в пам'яті 8 байт і зберігає одночасно посилання на об'єкт та адресу його методу.

type
TTextReader = class
private
FOnReadLine: TReadLineEvent;

public
property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;
end;

Методи об'єктів, оголошені за наведеним вище шаблоном, стають сумісні за типом з властивістю OnReadLine.

type
TForm1 = class(TForm)
procedure HandleLine(Reader: TTextReader; const Line: string);
end;

var
Form1: TForm1;
Reader: TTextReader;


Якщо встановити значення властивості OnReadLine:

Reader.OnReadLine := Form1.HandleLine;

і переписати метод NextLine,

function TTextReader.NextLine: Boolean;
var
S: string;
N: Integer;
begin
Result := not EndOfFile;
if Result then / / Якщо рядки для зчитування ще є, то
begin
Readln (FFile, S); / / Зчитування чергового рядка
N: = ParseLine (S); / / Виділення елементів рядка (розбір рядка)
if N <> ItemCount then
SetLength(FItems, N);
if Assigned(FOnReadLine) then
FOnReadLine (Self, S); / / повідомлення про читання чергового рядка
end;
end;

то об'єкт Form1 через метод HandleLine отримає повідомлення про чергову ліченої рядку. Зверніть увагу, що виклик методу через покажчик відбувається лише в тому випадку, якщо покажчик не дорівнює nil. Ця перевірка виконується за допомогою стандартної функції Assigned, яка повертає True, якщо її аргумент є пов'язаною покажчиком.

Описаний вище механізм називається делегуванням, оскільки він дозволяє передати частину роботи іншому об'єкту, наприклад, зосередити в одному об'єкті обробку подій, що виникають в інших об'єктах. Це позбавляє програміста від необхідності породжувати численні класи-спадкоємці і перекривати в них віртуальні методи. Делегування широко застосовується в середовищі Delphi. Наприклад, всі компоненти делегують обробку своїх подій тій формі, в яку вони поміщені.

Метакласи


Посилання на класи


Мова Delphi дозволяє розглядати класи об'єктів як свого роду об'єкти, якими можна маніпулювати в програмі. Така можливість народжує нове поняття – клас класу; Його прийнято позначати терміном метакласи.

Для підтримки метакласи введений спеціальний тип даних – посилання на клас (class reference). Він описується за допомогою словосполучення class of, Наприклад:

type
TTextReaderClass = class of TTextReader;

Змінна типу TTextReaderClass оголошується в програмі звичайним чином:

var
ClassRef: TTextReaderClass;

Значеннями змінної ClassRef можуть бути клас TTextReader і всі породжені від нього класи. Можливі наступні оператори:

ClassRef := TTextReader;
ClassRef := TDelimitedReader;
ClassRef := TFixedReader;

За аналогією з тим, як для всіх класів існує загальний предок TObject, у посилань на класи існує базовий тип TClass, визначений, як:

type
TClass = class of TObject;

Змінна типу TClass може посилатися на будь-який клас.

Практична цінність посилань на класи полягає в можливості створювати програмні модулі, що працюють з будь-якими класами об'єктів, навіть тими, які ще не розроблені.

Фізичний зміст і взаємозв'язок таких понять, як мінлива-об'єкт, екземпляр об'єкту в пам'яті, мінлива-клас і екземпляр класу в пам'яті пояснює малюнок 4.

Малюнок 4. Змінна-об'єкт, екземпляр об'єкту в пам'яті, мінлива-клас і екземпляр класу в пам'яті

Методи класів


Метакласи привели до виникнення нового типу методів – методів класу. Метод класу оперує не екземпляром об'єкта, а безпосередньо класом. Він оголошується як звичайний метод, але перед словом procedure або function записується зарезервоване слово class, Наприклад:

type
TTextReader = class

class function GetClassName: string;
end;

Рухаючись в метод класу неявний параметр Self містить не посилання на об'єкт, а посилання на клас, тому в тілі методу не можна звертатися до полів, методів і властивостей об'єкта. Зате можна викликати інші методи класу, наприклад:

class function TTextReader.GetClassName: string;
begin
Result := ClassName;
end;

Метод ClassName оголошений в класі TObject і повертає ім'я класу, до якого застосовується. Очевидно, що надуманий метод GetClassName просто дублює цю функціональність для класу TTextReader і всіх його спадкоємців.

Методи класу застосовні і до класів, і до об'єктів. В обох випадках у параметрі Self передається посилання на клас об'єкта. Приклад:

var
Reader: TTextReader;
S: string;
begin
/ / Виклик методу за допомогою посилання на клас
S: = TTextReader.GetClassName; / / S отримає значення TTextReader

/ / Створення об'єкта класу TDelimitedReader
Reader := TDelimitedReader.Create(MyData.del);

/ / Виклик методу за допомогою посилання на об'єкт
S: = Reader.GetClassName; / / S отримає значення TDelimitedReader
end.


Методи класів можуть бути віртуальними. Наприклад, в класі TObject визначений віртуальний метод класу NewInstance. Він служить для розподілу пам'яті під об'єкт і автоматично викликається конструктором. Його можна перекрити в своєму класі, щоб забезпечити нестандартний спосіб виділення пам'яті для примірників. Метод NewInstance повинен перекриватися разом з іншим методом FreeInstance, який автоматично викликається з деструктора і служить для звільнення пам'яті. Додамо, що розмір пам'яті, необхідний для екземпляра, можна дізнатися викликом зумовленого методу класу InstanceSize.

Віртуальні конструктори


Особлива краса посилань на класи проявляється в поєднанні з віртуальними конструкторами. Віртуальний конструктор оголошується з ключовим словом virtual. Виклик віртуального конструктора відбувається за фактичним значенням посилання на клас, а не з її формальному типу. Це дозволяє створювати об'єкти, класи яких невідомі на етапі компіляції. Механізм віртуальних конструкторів застосовується в середовищі Delphi при відновленні компонентів форми з файлу. Відновлення компонента відбувається наступним чином. З файлу зчитується ім'я класу. По цьому імені відшукується посилання на клас (метакласи). У метакласи викликається віртуальний конструктор, який створює об'єкт потрібного класу.

var
P: TComponent;
T: TComponentClass; // TComponentClass = class of TComponent;

T := FindClass(ReadStr);
P := T.Create(nil);


На цьому закінчимо вивчення теорії об'єктно-орієнтованого програмування і як практики розглянемо декілька широко використовуваних інструментальних класів середовища Delphi. Розберіться з їх призначенням і роботою. Це допоможе глибше зрозуміти ООП і стане в нагоді на майбутнє.

Класи загального призначення


Як показує практика, у більшості завдань доводиться використовувати однотипні структури даних: списки, масиви, множини і т.д. Від завдання до завдання змінюються лише їхні елементи, а методи роботи зберігаються. Наприклад, для будь-якого списку потрібні процедури вставки і видалення елементів. У зв'язку з цим виникає природне бажання вирішити задачу "в загальному вигляді", тобто створити універсальні засоби для управління основними структурами даних. Ця ідея не нова. Вона давно прийшла в голову розробникам інструментальних пакетів, які швидко наплодили безліч допоміжних бібліотек. Ці бібліотеки містили класи об'єктів для роботи зі списками, колекціями (динамічні масиви з перемінним кількістю елементів), словниками (колекції, індексовані рядками) та іншими "абстрактними" структурами. Для середовища Delphi теж розроблені аналогічні класи об'єктів. Їх велика частина зосереджена в модулі Classes. Найбільш потрібними для вас є списки рядків (TStrings, TStringList) і потоки (TSream, THandleSream, TFileStream, TMemoryStream і TBlobStream). Розглянемо коротко їх призначення та застосування.

Класи для подання списку рядків


Для роботи зі списками рядків служать класи TStrings і TStringList. Вони використовуються в бібліотеці VCL повсюдно і мають набагато більшу універсальність, ніж та, що можна почерпнути з їх назви. Класи TStrings і TStringList служать для подання не просто списку рядків, а списку елементів, кожен з яких представляє собою пару рядок-об'єкт. Якщо з рядками не асоційовані об'єкти, виходить звичайний список рядків.

Клас TStrings використовується візуальними компонентами і є абстрактним. Він не має власних засобів зберігання рядків і визначає лише інтерфейс для роботи з елементами. Клас TStringList є спадкоємцем TStrings і служить для організації списків рядків, які використовуються окремо від керуючих елементів. Об'єкти TStringList зберігають рядки і об'єкти в динамічній пам'яті.

Властивості класу TStrings описані нижче.


Спадкоємці класу TStrings іноді використовуються для зберігання рядків виду Ім'я = Значення, зокрема, рядків INI-файлів (див. гл. 6). Для зручної роботи з такими рядками в класі TStrings додатково є такі властивості.


Управління елементами списку здійснюється за допомогою таких методів:


Клас TStringList додає до TStrings кілька додаткових властивостей і методів, а також дві властивості-події для повідомлення про зміни в списку. Вони описані нижче.

Властивості:


Методи:


Події:


Нижче наводиться фрагмент програми, що демонструє створення списку рядків і маніпулювання його елементами:

var
Items: TStrings;
I: Integer;
begin
/ / Створення списку
Items := TStringList.Create;
Items.Add (Туризм);
Items.Add (Наука);
Items.Insert (1, Бізнес);

/ / Робота зі списком
for I := 0 to Items.Count – 1 do
Items[I] := UpperCase(Items[I]);

/ / Видалення списку
Items.Free;
end;

Класи для подання потоку даних


У середовищі Delphi існує ієрархія класів для зберігання і послідовного введення-виведення даних. Класи цій ієрархії називаються потоками. Потоки найкраще представляти як файли. Класи потоків забезпечують різне фізичне представлення даних: файл на диску, розділ оперативної пам'яті, поле в таблиці бази даних (таблиця 1).

Таблиця 1. Класи потоків
























Клас


Опис

TStream Абстрактний потік, від якого успадковуються всі інші. Властивості і методи класу TStream утворюють базовий інтерфейс потокових об'єктів.
THandleStream Потік, який зберігає свої дані у файлі. Для читання-запису файлу використовується дескриптор (handle), тому потік називається дескрипторних. Дескриптор – це номер відкритого файлу в операційній системі. Його повертають низькорівневі функції створення і відкриття файлу.
TFileStream Потік, який зберігає свої дані у файлі. Відрізняється від ThandleStream тим, що сам відкриває (створює) файл по імені, переданому в конструктор.
TMemoryStream Потік, який зберігає свої дані в оперативній пам'яті. Моделює роботу з файлом. Використовується для зберігання проміжних результатів, коли файловий потік не підходить із-за низької швидкості передачі даних.
TResourceStream Потік, що забезпечує доступ до ресурсів в Windows-додатку.
TBlobStream Забезпечує послідовний доступ до великих полях таблиць в базах даних.

Потоки широко застосовуються в бібліотеці VCL і напевно вам знадобляться. Тому нижче коротко перераховані їх основні загальні властивості і методи.

Загальні властивості:


Загальні методи:


Нижче наводиться фрагмент програми, що демонструє створення файлового потоку і запис в нього рядки:

var
Stream: TStream;
S: AnsiString;
StrLen: Integer;

begin
/ / Створення файлового потоку
Stream := TFileStream.Create(Sample.Dat, fmCreate);

/ / Запис у потік деякою рядки
StrLen := Length(S) * SizeOf(Char);
Stream.Write (StrLen, SizeOf (Integer)); / / запис довжини рядка
Stream.Write (S, StrLen); / / запис символів рядка

/ / Закриття потоку
Stream.Free;
end;


Підсумки


Тепер для вас немає секретів у світі ООП. Ви на досить серйозному рівні познайомилися з об'єктами та їх властивостями; дізналися, як об'єкти створюються, використовуються і знищуються. Якщо не все вдалося запам'ятати відразу – не біда. Повертайтеся до матеріалів глави по мірі вирішення поставлених перед вами завдань, і робота з об'єктами стане простою, природною і навіть приємною. Коли ви досягнете розуміння того, як працює один об'єкт, то автоматично зрозумієте, як працюють всі інші. Тепер ми розглянемо те, з чим ви зустрінетесь дуже скоро – помилки програмування.

наступна стаття серії


Посилання по темі



Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*