Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Постановка учебной задачи




Знакомство с техникой ООП в этом разделе иллюстрируется примерами, объединенными рамками следующей учебной задачи.

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

Рис. 10.1. Экран, создаваемый учебной программой

Для перемещения изображений в программе будут использоваться клавиши управления курсором, клавиши Ноте, End, PgUp, PgDn (для перемещения по диагональным направлениям) и клавиша Tab для выбора перемещаемого объекта. Выход из программы - клавиша Esc.

Техническая реализация программы потребует использования средств двух стандартных библиотек - CRT и GRAPH, которые еще не рассматривались в этой книге. Чтобы не отвлекать Ваше внимание от основных проблем ООП, при описании реализации учебной задачи особенности использования средств этих библиотек лишь очень кратко комментируются в текстах программы. Если Вы не привыкли «принимать на веру» предлагаемые программные решения и хотите разобраться с деталями вызова незнакомых Вам процедур и функций, рекомендую просмотреть материал гл.13 и гл.14, где описаны эти библиотеки (они не используют средств ООП и, следовательно, могут изучаться до чтения настоящей главы).

СОЗДАНИЕ ОБЪЕКТОВ

В Турбо Паскале для создания объектов используются три зарезервированных слова: object, constructor, destructor к три стандартные директивы: private, public и virtual.

Зарезервированное слово object используется для описания объекта. Описание объекта должно помещаться в разделе описания типов:

type

MyObject = object

(Поля объекта}

{Методы объекта}

end;

Если объект порождается от какого-либо родителя, имя родителя указывается в круглых скобках сразу за словом object:

type

MyDescendantObject = object(MyObject)

.

.

end;

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

Для нашей учебной задачи создадим объект-родитель TGraphObject, в рамках которого будут инкапсулированы поля и методы, общие для всех остальных объектов:

type

TGraphObj = object

Private {Поля объекта будут скрыты от пользователя}

X,Y: Integer; {Координаты реперной точки}

Color: Word; {Цвет фигуры}

Public {Методы объекта будут доступны пользователю}

Constructor Init(aX,aY: Integer; aColor: Word);

{Создает экземпляр объекта}

Procedure Draw(aColor: Word); Virtual;

{Вычерчивает объект заданным цветом aColor}

Procedure Show;

{Показывает объект - вычерчивает его цветом Color}

Procedure Hide;

{Прячет объект - вычерчивает его цветом фона}

Procedure MoveTo(dX,dY: Integer);

{Перемещает объект в точку с координатами X+dX и Y+dY}

end; {Конец описания объекта TGraphObj}

В дальнейшем предполагается создать объекты-потомки от TGraphObj, реализующие все специфические свойства точки, линии, окружности и прямоугольника. Каждый из этих графических объектов будет характеризоваться положением на экране (поля X и Y) и цветом (поле Color). С помощью метода Draw он будет способен отображать себя на экране, а с помощью свойств «показать себя» (метод Show) и «спрятать себя» (метод Hide) сможет перемещаться по экрану (метод MoveTo). Учитывая общность свойств графических объектов, мы объявляем абстрактный объект TGraphObj, который не связан с конкретной графической фигурой. Он объединяет в себе все общие поля и методы реальных фигур и будет служить родителем для других объектов.

Директива Private в описании объекта открывает секцию описания скрытых полей и методов. Перечисленные в этой секции элементы объекта «не видны» программисту, если этот объект он получил в рамках библиотечного ТР(/-модуля. Скрываются обычно те поля и методы, к которым программист (в его же интересах!) не должен иметь непосредственного доступа. В нашем примере он не может произвольно менять координаты реперной точки (X.Y), т.к. это не приведет к перемещению объекта. Для изменения полей X и Y предусмотрены входящие в состав объекта методы Init и MoveTo. Скрытые поля и методы доступны в рамках той программной единицы (программы или модуля), где описан соответствующий объект. В дальнейшем предполагается, что программа будет использовать модуль GraphObj с описанием объектов. Скрытые поля будут доступны в модуле GraphObj, но недоступны в использующей его основной программе. Разумеется, в рамках реальной задачи создание скрытых элементов объекта вовсе необязательно. Я ввел их в объект TGraphObj лишь для иллюстрации возможностей ООП.

Директива public отменяет действие директивы private, поэтому все следующие за public элементы объекта доступны в любой программной единице. Директивы private и public могут произвольным образом чередоваться в пределах одного объекта.

Вариант объявления объекта TGraphObj без использования механизма private...public:

type

TGraphObj = object

X,Y: Integer;

Color: Word;

Constructor Init(aX,aY: Integer; aColor: Word);

Procedure Draw(aColor: Word); Virtual;

Procedure Show;

Procedure Hide;

Procedure MoveTo(dX,dY: Integer);

end;

Описания полей ничем не отличаются от описания обычных переменных. Полями могут быть любые структуры данных, в том числе и другие объекты. Используемые в нашем примере поля X и Y содержат координату реперной (характерной) точки графического объекта, а поле Color - его цвет. Реперная точка характеризует текущее положение графической фигуры на экране и, в принципе, может быть любой ее точкой.

В нашем примере она совпадает с координатами точки в описываемом ниже объекте TPoint, с центром окружности в объекте TCircle, первым концом прямой в объекте TLine и с левым верхним углом прямоугольника в объекте TRect.

Для описания методов в ООП используются традиционные для Паскаля процедуры и функции, а также особый вид процедур - конструкторы и деструкторы. Конструкторы предназначены для создания конкретного экземпляра объекта, ведь объект - это тип данных, т.е. «шаблон», по которому можно создать сколько угодно рабочих экземпляров данных объектного типа (типа TGraphOhj, например). Зарезервированное слово constructor, используемое в заголовке конструктора вместо procedure, предписывает компилятору создать особый код пролога, с помощью которого настраивается так называемая таблица виртуальных методов (см. ниже). Если в объекте нет виртуальных методов, в нем может не быть ни одного конструктора, наоборот, если хотя бы один метод описан как виртуальный (с последующим словом Virtual, см. метод Draw), в состав объекта должен входить хотя бы один конструктор и обращение к конструктору должно предшествовать обращению к любому виртуальному методу.

Типичное действие, реализуемое конструктором, состоит в наполнении объектных полей конкретными значениями. Следует заметить, что разные экземпляры одного и того же объекта отличаются друг от друга только содержимым объектных полей, в то время как каждый из них использует одни и те же объектные методы. В нашем примере конструктор Init объекта TGraphObj получает все необходимые для полного определения экземпляра данные через параметры обращения аХ, аY и aColor.

Процедура Draw предназначена для вычерчивания графического объекта. Эта процедура будет реализовываться в потомках объекта TGraphObj по-разному. Например, для визуализации точки следует вызвать процедуру PutPixel, для вычерчивания линии - процедуру Line и т.д. В объекте TGraphObj процедура Draw определена как виртуальная («воображаемая»). Абстрактный объект TGraphObj не предназначен для вывода на экран, однако наличие процедуры Draw в этом объекте говорит о том, что любой потомок TGraphObj должен иметь собственный метод Draw, с помощью которого он может показать себя на экране.

При трансляции объекта, содержащего виртуальные методы, создается так называемая таблица виртуальных методов (ТВМ), количество элементов которой равно количеству виртуальных методов объекта. В этой таблице будут храниться адреса точек входа в каждый виртуальный метод. В нашем примере ТВМ объекта TGraphObj хранит единственный элемент - адрес метода Draw. Первоначально элементы ТВМ не содержат конкретных адресов. Если бы мы создали экземпляр объекта TGraphObj с помощью вызова его конструктора Init, код пролога конструктора поместил бы в ТВМ нужный адрес родительского метода Draw. Далее мы создадим несколько потомков объекта TGraphObj. Каждый из них будет иметь собственный конструктор, с помощью которого ТВМ каждого потомка настраивается так, чтобы ее единственный элемент содержал адрес нужного метода Draw. Такая процедура называется поздним связыванием объекта. Позднее связывание позволяет методам родителя обращаться к виртуальным методам своих потомков и использовать их для реализации специфичных для потомков действий.

Наличие в объекте TGraphObj виртуального метода Draw позволяет легко реализовать три других метода объекта: чтобы показать объект на экране в методе Show, вызывается Draw с цветом aColor, равным значению поля Color, а чтобы спрятать графический объект, в методе Hide вызывается Draw со значением цвета GetBkColor, т.е. с текущим цветом фона.

Рассмотрим реализацию перемещения объекта. Если потомок TGraphObj (например, TLine) хочет переместить себя на экране, он обращается к родительскому методу MoveTo. В этом методе сначала с помощью Hide объект стирается с экрана, а затем с помощью Show показывается в другом месте. Для реализации своих действий и Hide, и Show обращаются к виртуальному методу Draw. Поскольку вызов MoveTo происходит в рамках объекта TLine, используется ТВМ этого объекта и вызывается его метод Draw, вычерчивающий прямую. Если бы перемешалась окружность, ТВМ содержала бы адрес метода Draw объекта TCircle и визуализация-стирание объекта осуществлялась бы с помощью этого метода.

Чтобы описать все свойства объекта, необходимо раскрыть содержимое объектных методов, т.е. описать соответствующие процедуры и функции. Описание методов производится обычным для Паскаля способом в любом месте раздела описаний, но после описания объекта. Например:

type

TGraphObj = object

...

end;

Constructor TGraphObj.Init;

begin

X:= aX;

Y:= aY; Color:= aColor

end;

Procedure TGraphObj-Draw;

begin

{Эта процедура в родительском объекте ничего не делает, поэтому экземпляры TGraphObj не способны отображать себя на экране. Чтобы потомки объекта TGraphObj были способны отображать себя, они должны перекрывать этот метод}

end;

Procedure TGraphObj.Show;

begin

Draw(Color)

end;

Procedure TGraphObj.Hide;

begin

Draw(GetBkColor)

end;

Procedure TGraphObj.MoveTo;

begin

Hide;

X:= X+dX;

Y:= Y+dY;

Show

end;

Отмечу два обстоятельства. Во-первых, при описании методов имя метода дополняется спереди именем объекта, т.е. используется составное имя метода. Это необходимо по той простой причине, что в иерархии родственных объектов любой из методов может быть перекрыт в потомках. Составные имена четко указывают принадлежность конкретной процедуры. Во-вторых, в любом объектном методе можно использовать инкапсулированные поля объекта почти так, как если бы они были определены в качестве глобальных переменных. Например, в конструкторе TGraph.Init переменные в левых частях операторов присваивания представляют собой объектные поля и не должны заново описываться в процедуре. Более того, описание

Constructor TGraphObj.Init;

var

X,Y: Integer; {Ошибка!}

Color: Word; {Ошибка!}

begin

end;

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

Обратите внимание: абстрактный объект TGraphObj не предназначен для вывода на экран, поэтому его метод Draw ничего не делает. Однако методы Hide, Show и MoveTo «знают» формат вызова этого метода и реализуют необходимые действия, обращаясь к реальным методам Draw своих будущих потомков через соответствующие ТВМ. Это и есть полиморфизм объектов.

Создадим простейшего потомка от TGraphObj - объект TPoint, с помощью которого будет визуализироваться и перемещаться точка. Все основные действия, необходимые для этого, уже есть в объекте TGraphObj, поэтому в объекте TPoint перекрывается единственный метод - Draw.

type

TPoint = object(TGraphObj)

Procedure Draw(aColor); Virtual;

end;

Procedure TPoint.Draw;

begin

PutPixel(X,Y,Color) {Показываем цветом Color пиксель с координатами X и Y}

end;

В новом объекте TPoint можно использовать любые методы объекта-родителя TGraphObj. Например, вызвать метод MoveTo, чтобы переместить изображение точки на новое место. В этом случае родительский метод TGraphObj.MoveTo будет обращаться к методу TPoint.Draw, чтобы спрятать и затем показать изображение точки. Такой вызов станет доступен после обращения к конструктору Init объекта TPoint, который нужным образом настроит ТВМ объекта. Если вызвать TPoint.Draw до вызова Init, его ТВМ не будет содержать правильного адреса и программа «зависнет».

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

type

TLine = object(TGraphObj)

dX,dY: Integer; {Приращения координат второго конца}

Constructor Init(X1,Y1,X2,Y2: Integer; aColor: Word);

Procedure Draw(aColor: Word); Virtual;

end;,

Constructor TLine.Init;

{Вызывает унаследованный конструктор TGraphObj для инициации полей X, Y и Color. Затем инициирует поля dX и dY}

begin

{Вызываем унаследованный конструктор}

Inherited Init(XI,Yl,aColor);

{Инициируем поля dX и dY}

dX:= Х2-Х1;

dY:= Y2-Y1

end;

Procedure Draw;

begin

SetColor(Color); {Устанавливаем цвет Color}

Line(X,Y,X+dX,Y+dY) {Вычерчиваем линию}

end;

В конструкторе TLine.Init для инициации полей X, Y и Color, унаследованных от родительского объекта, вызывается унаследованный конструктор TGraph.Init, для чего используется зарезервированное слово inherited (англ.- унаследованный):

Inherited Init(XI,Yl,aColor);

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

TGraphObj.Init(Xl,Yl,aColor);

Для инициации полей dX и dY вычисляется расстояние в пикселах по горизонтали и вертикали от первого конца прямой до ее второго конца. Это позволяет в методе TLine.Draw вычислить координаты второго конца по координатам первого и смещениям dX и dY. В результате простое изменение координат реперной точки X, Y в родительском методе TGraph.MoveTo перемещает всю фигуру по экрану.

Теперь нетрудно реализовать объект TCircle для создания и перемещения окружности:

type

TCircle = object(TGraphObj)

R: Integer; {Радиус}

Constructor Init(aX,aY,aR: Integer;

Procedure Draw(aColor: Virtual);

end;

Constructor TCircle.Init;

begin

Inherited Init(aX,aY,aColor);

R:= aR

end;

aColor: Word)

Procedure TCircle.Draw;

begin

SetColor(aColor); {Устанавливаем цвет Color}

Circle(X,Y,R) {Вычерчиваем окружность}

end;

В объекте TRect, с помощью которого создается и перемещается прямоугольник, учтем то обстоятельство, что для задания прямоугольника требуется указать четыре целочисленных параметра, т.е. столько же, сколько для задания линии. Поэтому объект TRect удобнее породить не от TGraphObj, а от TLine, чтобы использовать его конструктор Init:

type

TRect = object(TLine)

Procedure Draw(aColor: Word);

end;

Procedure TRect.Draw;

begin

SetColor(aColor);

Rectangle(X,Y,X+dX,Y+dY) {Вычерчиваем прямоугольник}

end;

Чтобы описания графических объектов не мешали созданию основной программы, оформим эти описания в отдельном модуле GraphObj:

Unit GraphObj; Interface

{Интерфейсная часть модуля содержит только объявления объектов}

type

TGraphObj = object

...

end;

TPoint = object(TGraphObj)

...

end;

TLine = object(TGraphObj)

...

end;

TCircle = object(TGraphObj)

end;

TRect = object(TLine)

...

end;

Implementation

{Исполняемая часть содержит описания всех объектных методов}

Uses Graph;

Constructor TGraphObj.Init;

...

end.

В интерфейсной части модуля приводятся лишь объявления объектов, подобно тому как описываются другие типы данных, объявляемые в модуле доступными для внешних программных единиц. Расшифровка объектных методов помещается в исполняемую часть implementation, как если бы это были описания обычных интерфейсных процедур и функций. При описании методов можно опускать повторное описание в заголовке параметров вызова. Если они все же повторяются, они должны в точности соответствовать ранее объявленным параметрам в описании объекта. Например, заголовок конструктора TGraphObj.Init может быть таким:

Constructor TGraphObj.Init;

или таким:

Constructor TGraphObj.Init(aX,aY: Integer; aColor: Word);

Поделиться:





Читайте также:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...