Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi / песни о паскале.pdf
Скачиваний:
63
Добавлен:
26.03.2016
Размер:
5.16 Mб
Скачать

Глава 61 «Кубики» программиста (ООП)

var P : TPerson;

{ переменная-объект }

begin

P.Init(1995, 'Мария', 'Рыбкина');

Так ни одно поле объекта не будет пропущено, — за этим присмотрит компилятор!

Вот пока всё, что следует сказать об инкапсуляции. Приведенный ниже пример P_61_2 демонстрирует объект типа TPerson: здесь описана его структура и реализация методов, а затем объявлены две переменные, выполнена их инициализация и распечатка полей.

 

{ P_61_2 Программа с применением объекта типа «человек» (персона) }

type

TPerson = object

 

 

 

 

mBearing : integer;

{ год рождения }

 

 

 

mName

: string;

{ имя }

 

 

 

mFam

: string;

{ фамилия }

 

 

 

constructor Init(aBearing: integer; const aName, aFam : string);

 

procedure Report;

{ процедура распечатки объекта }

 

end;

 

 

 

 

 

{--- Реализация двух методов объекта ---}

 

 

constructor TPerson.Init(aBearing: integer; const aName, aFam : string);

begin

mBearing := aBearing; mName

:= aName;

mFam

:= aFam;

end;

 

 

 

 

 

procedure TPerson.Report;

 

 

 

begin

Writeln(mBearing:6, 'Фамилия: '+mFam:20, ' Имя: '+mName);

end;

 

 

 

 

 

var P1, P2 : TPerson; { две переменных объектного типа }

 

begin

{--- Главная программа ---}

 

 

 

P1.Init(1985, 'Иван', 'Грозный');

 

 

P2.Init(1995, 'Мария', 'Рыбкина');

P1.Report;

P2.Report;

Readln;

end.

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

Кажется, что инкапсуляция не упростила программирование. Да, верно, если рассматривать её в отрыве от других механизмов ООП: наследования и полиморфизма. Выигрыш увидим, когда в ход пойдут все рычаги.

505

Глава 61 «Кубики» программиста (ООП)

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

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

И это не всё. Постройка одних объектов на основе других формирует иерархию родственных объектов. С разными объектами этой иерархии можно обращаться сходным образом — это и есть полиморфизм. Буквальный перевод этого слова — «многоструктурность» — почти ничего не объясняет. Принципы наследования и полиморфизма легче понять на примере.

Приборостроение

Я знаю, чем напичкано ваше жилище — электрическими приборами. И простыми, такими, как лампочка или утюг. И сложными: телевизор, стиральная машина, компьютер, наконец. Взглянем на них глазами программиста: любой такой прибор, выражаясь языком ООП, обладает, по крайней мере, двумя общими методами: включить и отключить. В разных приборах эти операции выполняются по-разному, но в целом они сходны. Можно сказать, что эти методы

— общие свойства всех электроприборов.

Если бы приборы создавал программист, то построил бы их на базе общего предка — абстрактного (воображаемого) электрического прибора. Этот прибор обладал бы двумя методами: включить и отключить. На Паскале этот абстрактный электроприбор можно объявить так:

type Электроприбор = object

procedure Включить; virtual;

procedure Отключить; virtual;

end;

Здесь встречаем новое волшебное словечко — VIRTUAL, что значит «воображаемый». Это ключевое слово Паскаля следует за объявлением тех методов объекта, которые разрешено изменять в его наследниках. Изменение метода в наследниках называют переопределением метода. Итак, слово VIRTUAL указывает компилятору, что в наследниках методы включения и отключения прибора могут быть изменены в соответствии с особенностями этих наследников. К примеру, лампочка и телевизор включаются по-разному.

506

Глава 61 «Кубики» программиста (ООП)

Учредив абстрактный электроприбор, построим на нём прибор «чисто конкретный», например, телевизор.

type Телевизор = object (Электроприбор)

procedure Включить; virtual;

procedure Отключить; virtual;

procedure Выбрать_канал;

procedure Настроить_громкость;

procedure Настроить_яркость;

end;

Поскольку телевизор порожден от электроприбора, название его предка — «электроприбор» — указано в скобках за ключевым словом OBJECT. Наследник обязан помнить о предке, ссылаться на него, иначе не получит своего наследства

— полей и методов. Виртуальные методы включить и отключить объявлены в наследнике точно так же, но будут реализованы иначе. К ним добавлены ещё три метода, характерные именно для телевизора. Схожим образом строятся и другие «конкретные» электроприборы. В результате сформируется иерархия родственных объектов, показанная на рис. 153.

Предок

Электроприбор: Наследники Включить Отключить

 

Телевизор:

 

 

 

Стиральная машина:

 

 

 

Лампочка:

 

 

 

 

 

 

Включить

 

 

 

Включить

 

 

 

Включить

 

 

Отключить

 

 

 

Отключить

 

 

 

Отключить

 

 

Выбрать канал

 

 

 

Выбрать режим

 

 

 

 

 

 

Настроить громкость

 

 

 

Пуск

 

 

 

 

 

 

Настроить яркость

 

 

 

Стоп

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 153 – Иерархия электрических приборов

Гражданское строительство

Но всё это лишь присказка, теперь испытаем наследование и полиморфизм в деле. Создадим на базе спроектированного ранее объекта TPerson (человек) два новых типа данных: военнослужащий (TMilitary) и гражданский чиновник (TCivil), иерархия этих типов изображена на рис. 154. Эти новые типы «людей» будут содержать дополнительные поля с характерной для наследников информацией. Вдобавок изменим конструктор Init и метод Report с тем, чтобы

507

Глава 61 «Кубики» программиста (ООП)

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

Предок

Наследники

TPerson

 

TMilitary

 

 

 

TCivil

 

 

 

 

 

 

 

 

 

 

 

Рис. 154 - Иерархия «человеческих» типов

Объявление

Начнем с военнослужащего, чем разнится он от простых смертных? Гордым воинским званием — от рядового до маршала. Для хранения воинского звания в объекте TMilitary добавим строковое поле mRank (Rank — звание). Ясно, что при создании объекта конструктором надо указать этот элемент. Добавим ещё один параметр конструктору объекта Init — параметр aRank, и тогда заголовок конструктора в объекте TMilitary станет таким.

constructor Init(aBearing: integer; const aName, aFam, aRank : string);

В новом конструкторе больше параметров, и работать он будет, в сравнении с предком, чуть иначе. Другими словами, в наследнике он переопределен. А если так, то где же волшебное слово VIRTUAL? Его здесь нет и не должно быть, поскольку конструктор виртуален по определению.

Теперь обратимся к процедуре распечатки Report. В наследнике она, кроме прочего, должна распечатать поле воинского звания, а значит, будет переопределена. Поэтому и объявлена виртуальной, причем и в наследнике TMilitary, и в его предке TPerson. Так сделано потому, что лишь виртуальный метод предка может быть виртуальным у наследника: виртуальность передается по наследству. С учетом всего сказанного, объявления типов TPerson и TMilitary теперь будут такими.

508

Глава 61 «Кубики» программиста (ООП)

TPerson = object

 

mBearing : integer;

{ год рождения }

mName

: string;

{ имя }

mFam

: string;

{ фамилия }

constructor Init(aBearing: integer; const aName, aFam : string);

procedure Report; virtual;

end;

 

 

TMilitary = object (TPerson)

 

mRank

: string;

{ воинское звание }

constructor Init(aBearing: integer; const aName, aFam, aRank : string);

procedure Report; virtual;

end;

Подытожим все изменения. В предке TPerson процедура Report стала виртуальной. В наследнике TMilitary добавлено поле mRank, а также изменены два метода: конструктор и процедура Report.

Отселение в отдельный модуль

Настало время реализовать методы наследника. Но прежде, чем взяться за это, совершим одно полезное дельце — переместим объект-предок TPerson в отдельный модуль. Именно так поступают профессионалы, создавая библиотеки объектов. Порядок создания программного модуля подробно изложен в главе 59, вкратце я напомню основные шаги.

Итак, создайте новый файл, перенесите туда через буфер обмена объявление типа TPerson и реализацию его методов. Объявление объекта разместите в секции INTERFACE модуля, а реализацию — в секции IMPLEMENTATION. И не забудьте объявить виртуальной процедуру Report. Дайте модулю имя PERSON и сохраните под именем «PERSON.PAS». У вас получится файл, показанный ниже. В нём объявлен ещё один тип данных — указатель на объект PPerson, но к нему обратимся позже.

509

Глава 61 «Кубики» программиста (ООП)

unit Person; { Модуль, содержащий описание и реализацию объекта «ЧЕЛОВЕК» }

interface

 

 

 

type PPerson = ^TPerson;

{ указатель на объект «ЧЕЛОВЕК» }

TPerson = object

 

 

mBearing : integer;

{ год рождения }

mName

: string;

{ имя }

mFam

: string;

{ фамилия }

constructor Init(aBearing: integer; const aName, aFam : string); procedure Report; virtual;

end;

implementation

{--- Реализация объекта «ЧЕЛОВЕК» ---}

constructor TPerson.Init(aBearing: integer; const aName, aFam : string); begin

mBearing

:= aBearing;

mName

:= aName;

mFam

:= aFam;

end;

 

procedure TPerson.Report; begin

Writeln(mBearing:6, 'Фамилия: '+mFam:20, ' Имя: '+mName); end;

end.

Теперь то, что переехало в модуль Person, из первичного файла проекта удалим, и добавим в начале программы ссылку для импорта модуля.

USES Person;

Сохраните новую версию файла под именем P_61_3 и убедитесь, что она компилируется без ошибок. Затем вставьте в первичный файл приведенное ранее объявление типа для наследника TMilitary. В итоге заготовка будущей программы станет такой.

510

Глава 61 «Кубики» программиста (ООП)

{ P_61_3 – Демонстрация принципов наследования и полиморфизма }

uses Person; { Объект TPerson импортируется из модуля Person }

type { объект «ВОЕННОСЛУЖАЩИЙ» }

TMilitary = object (TPerson)

mRank : string; { воинское звание }

constructor Init(aBearing: integer; const aName, aFam, aRank : string); procedure Report; virtual;

end;

begin end.

Реализация методов

Отсюда приступим к реализации переопределенных методов нового объекта. Начнем с конструктора. Конечно, он мог бы повторить действия объекта-предка, но это неразумно. Ведь цель объектной технологии — упростить программирование, избежать повторов, не так ли? Избежать повтора здесь очень просто: внутри конструктора наследника вызовем конструктор предка, передав ему нужные параметры.

TPerson.Init(aBearing, aName, aFam);

Вызов конструктора предка содержит имя этого предка — префикс TPerson. Обращение потомка к методам предка — обычная практика. По этой причине в Паскале учреждено ключевое слово INHERITED — «унаследованный». Если предварить им вызов унаследованного метода, то префикс с именем предка станет излишним.

inherited Init(aBearing, aName, aFam);

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

Итак, поля, унаследованные от предка, инициализированы конструктором, унаследованным от него же. Оставшееся поле mRank заполним как обычно, в результате конструктор наследника примет такой вид.

511

Соседние файлы в папке delphi