Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Введение в ООП.doc
Скачиваний:
34
Добавлен:
17.09.2019
Размер:
488.96 Кб
Скачать

Понятие наследования

Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее важная черта классов — способность наследовать поля, методы и свойства других классов. Чтобы пояснить сущность наследования обратимся к примеру с читателем текстовых файлов в формате "delimited text".

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

type

TFixedReader = class

private

// Поля

FFile: TextFile;

FItems: array of string;

FActive: Boolean;

FItemWidths: array of Integer;

// Методы чтения и записи свойств

procedure SetActive(const AActive: Boolean);

function GetItemCount: Integer;

function GetEndOfFile: Boolean;

function GetItem(Index: Integer): string;

// Методы

procedure PutItem(Index: Integer; const Item: string);

function ParseLine(const Line: string): Integer;

function NextLine: Boolean;

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

constructor Create(const FileName: string;

const AItemWidths: array of Integer);

destructor Destroy; override;

// Свойства

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;

{ TFixedReader }

constructor TFixedReader.Create(const FileName: string;

const AItemWidths: array of Integer);

var

I: Integer;

begin

AssignFile(FFile, FileName);

FActive := False;

// Копирование AItemWidths в FItemWidths

SetLength(FItemWidths, Length(AItemWidths));

for I := 0 to High(AItemWidths) do

FItemWidths[I] := AItemWidths[I];

end;

destructor TFixedReader.Destroy;

begin

Active := False;

end;

function TFixedReader.GetEndOfFile: Boolean;

begin

Result := Eof(FFile);

end;

function TFixedReader.GetItem(Index: Integer): string;

begin

Result := FItems[Index];

end;

function TFixedReader.GetItemCount: Integer;

begin

Result := Length(FItems);

end;

function TFixedReader.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;

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;

procedure TFixedReader.PutItem(Index: Integer; const Item: string);

begin

if Index > High(FItems) then // Если индекс выходит за границы массива,

SetLength(FItems, Index + 1); // то увеличение размера массива

FItems[Index] := Item; // Установка соответствующего элемента

end;

procedure TFixedReader.SetActive(const AActive: Boolean);

begin

if Active <> AActive then // Если состояние изменяется

begin

if AActive then

Reset(FFile) // Открытие файла

else

CloseFile(FFile); // Закрытие файла

FActive := AActive; // Сохранение состояния в поле

end;

end;

Поля, свойства и методы класса TFixedReader практически полностью аналогичны тем, что определены в классе TDelimitedReader. Отличие состоит в отсутствии свойства Delimiter, наличии поля FItemWidths (для хранения размеров элементов), другой реализации метода ParseLine и немного отличающемся конструкторе. Если в будущем появится класс для чтения элементов из файла еще одного формата (например, зашифрованного текста), то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования общих атрибутов (полей, свойств и методов) при определении новых классов, воспользуемся механизмом наследования. Прежде всего, выделим в отдельный класс TTextReader общие атрибуты всех классов, предназначенных для чтения элементов из текстовых файлов. Реализация методов TTextReader, кроме метода ParseLine, полностью идентична реализации TDelimitedReader, приведенной в предыдущем разделе.

type

TTextReader = class

private

// Поля

FFile: TextFile;

FItems: array of string;

FActive: Boolean;

// Методы получения и установки значений свойств

procedure SetActive(const AActive: Boolean);

function GetItemCount: Integer;

function GetItem(Index: Integer): string;

function GetEndOfFile: Boolean;

// Методы

procedure PutItem(Index: Integer; const Item: string);

function ParseLine(const Line: string): Integer;

function NextLine: Boolean;

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

constructor Create(const FileName: string);

destructor Destroy; override;

// Свойства

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;

...

constructor TTextReader.Create(const FileName: string);

begin

AssignFile(FFile, FileName);

FActive := False;

end;

function TTextReader.ParseLine(const Line: string): Integer;

begin

// Функция просто возвращает 0, поскольку не известно,

// в каком именно формате хранятся элементы

Result := 0;

end;

...

При реализации класса TTextReader ничего не известно о том, как хранятся элементы в считываемых строках, поэтому метод ParseLine ничего не делает. Очевидно, что создавать объекты класса TTextReader не имеет смысла. Для чего тогда нужен класс TTextReader? Ответ: чтобы на его основе определить (породить) два других класса — TDelimitedReader и TFixedReader, предназначенных для чтения данных в конкретных форматах:

type

TDelimitedReader = class(TTextReader)

FDelimiter: Char;

function ParseLine(const Line: string): Integer; override;

constructor Create(const FileName: string; const ADelimiter: Char = ';');

property Delimiter: Char read FDelimiter;

end;

TFixedReader = class(TTextReader)

FItemWidths: array of Integer;

function ParseLine(const Line: string): Integer; override;

constructor Create(const FileName: string;

const AItemWidths: array of Integer);

end;

...

Классы TDelimitedReader и TFixedReader определены как наследники TTextReader (об этом говорит имя в скобках после слова class). Они автоматически включают в себя все описания, сделанные в классе TTextReader и добавляют к ним некоторые новые. В результате формируется дерево классов, показанное на рисунке 3.1 (оно всегда рисуется перевернутым).

Рисунок 3.1. Дерево классов

Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком. Соответственно класс, от которого происходит наследование, выступает в роли базового, или предка. В нашем примере класс TDelimitedReader является прямым потомком класса TTextReader. Если от TDelimitedReader породить новый класс, то он тоже будет потомком класса TTextReader, но уже не прямым.

Очень важно, что в отношениях наследования любой класс может иметь только одного непосредственного предка и сколь угодно много потомков. Поэтому все связанные отношением наследования классы образуют иерархию. Примером иерархии классов является библиотека VCL; с ее помощью в среде Delphi обеспечивается разработка GUI-приложений.

Полиморфизм и виртуальные методы

Полиморфизм — это возможность использовать одинаковые имена для методов, входящих в различные классы. Концепция полиморфизма обеспечивает з случае применения метода к объекту использование именно того метода, <оторый соответствует классу объекта.

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

tуре

// базовый класс TPerson = class

fname: string; // имя

constructor Create(name:string);

function info: string;

virtual;

end;

// производный от TPerson TStud = class(TPerson)

fgr:integer; // номер учебной труппы

constructor Create(name:string;gr:integer);

function info: string; override; end;

// производный от TPerson TProf = class(TPerson)

fdep:string; // название кафедры

constructor Create(name:string;dep:string);

function info: string;

override;

end;

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

Ниже приведено определение метода info для каждого класса.

function TPerson.info:string;

begin

result := '';

end;

function TStud.info:string;

begin

result := fname + ' гp.' + IntTostr(fgr);

end;

function TProf.info:string;

begin

result := fname + ' каф.' + fdep;

end;

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

list: array[l..SZL] of TPerson;

Объявить подобным образом список можно потому, что язык Delphi позволяет указателю на родительский класс присвоить значение указателя на дочерний класс. Поэтому элементами массива list могут быть как объекты класса TStud, так и объекты класса TProf.

Вывести список студентов и преподавателей можно применением метода info к элементам массива. Например, так:

st := '';

for i:=l to SZL do // SZL - размер массива-списка

if list[i] о NIL

then st := st + list[i].Info

+ #13; ShowMessage (st);

Во время работы программы каждый элемент массива может содержать как объект типа xstud, так и объект типа TProf. Концепция полиморфизма обеспечивает применение к объекту именно того метода, который соответствует типу объекта.

Следующая программа, используя рассмотренные выше объявления классов TPerson, TStud и TProf, формирует и выводит список студентов и преподавателей. Текст программы приведен в листинге 9.1, а диалоговое окно — на рис. 9.1.

Рис. 9.1. Диалоговое окно программы Полиморфизм

Листинг 9.1. Демонстрация полиморфизма

unit polimor_;

interface

uses

Windows, Messages, SysUtils, Classes,

Graphics, Controls, Forms, Dialogs, StdCtrls;

type

TForm1 = class(TForm) Edit1: TEdit;

Edit2: TEdit;

GroupBoxl: TGroupBox;

RadioButton1: TRadioButton;

RadioButton2: TRadioButton;

Label1: TLabel;

Label2: TLabel;

Button1: TButton;

Button2: TButton;

procedure ButtonlClick(Sender: TObject);

procedure Button2Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

type

// базовый класс

TPerson = class

fName: string; // имя

constructor Create(name:string);

function info:string; virtual;

end;

// класс Студент TStud = class(TPerson)

fGr:integer; // номер группы

constructor Create(name:string;gr:integer);

function info:string;

override;

end;

// класс Преподаватель

TProf = class (TPerson)

fdep:string; // название кафедры

constructor Create(name:string;dep:string);

function info:string;

override;

end;

const

SZL = 10; // размер списка

var

Forml: TForm1;

List: array[l..SZL] of TPerson; // список

n:integer =0; // кол-во людей в списке

implementation

{$R *.DFM}

constructor TPerson.Create(name:string);

begin

fName := name; end;

constructor TStud.Create(name:string;gr:integer);

begin

inherited create(name); // вызвать конструктор базового класса

fGr := gr; end;

constructor TProf.create(name:string; dep:string);

begin

inherited create(name); // вызвать конструктор базового класса

fDep := dep; end;

function TPerson.Info:string;

begin

result := fname; end;

function TStud.Info:string;

begin

result := fname + ' rp.' + IntToStr(fGr); end;

function TProf.Info:string;

begin

result := fname + ' каф.' + fDep;

end;

// щелчок на кнопке Добавить

procedure TForml.ButtonlClick(Sender: TObject);

begin

if n < SZL then begin

// добавить объект в список

n:=n+l;

if Radiobuttonl.Checked

then // создадим объект TStud

List[n]:=TStud.Create(Edit1.Text,StrToInt(Edit2.Text))

else // создать объект TProf

List[n]:=TProf.Create(Edit1.Text,Edit2.Text); // очистить поля ввода

Edit1.Text := '' ; Edit2.Text := '';

Edit1.SetFocus; // курсор в поле Фамилия

end

else ShowMessage('Список заполнен!');

end;

// щелчок на кнопке Список

procedure TForm1.Button2Click(Sender: TObject);

var

i:integer; // индекс

st:string; // список begin

for i:=1 to SZL do

if list[i] <> NIL then st:=st + list[i].info + 113;

ShowMessage('Cпиcoк'+#13+st); end;

end.

Процедура TForml.Buttoniciick, которая запускается нажатием кнопки Добавить (Buttonl), создает объект iist[n] класса TStud или TProf. Класс создаваемого объекта определяется состоянием переключателя RadioButton. Установка переключателя в положение студент (RadioButtoni) определяет класс TStud, а в положение преподаватель (RadioButton2) — класс TProf.

Процедура TForm1.Button2Сlick, которая запускается нажатием кнопки Список (Button2), применяя метод info к каждому объекту списка (элементу массива), формирует строку, представляющую собой весь список.