Полиформные коллекции
Как уже говорилось, коллекции Turbo Vision обладают свойством полиморфизма -они позволяют хранить различные объекты. Поскольку каждый объект имеет все необходимые для него поля и методы, работа с полиморфными коллекциями не создает дополнительных проблем. Действительно, в полиморфной коллекции Вам обычно нет нужды следить за тем, какого типа объект хранится в том или ином элементе - достаточно вызвать нужный виртуальный метод, чтобы осуществить над элементом требуемые действия.
Рассмотрим следующий пример. Пусть необходимо создать и использовать библиотеку графических примитивов (точки, окружности, прямоугольники и т.п.). Каждый из этих элементов может отличаться своим набором полей и методов. Однако некоторые методы выполняют над объектами однотипные действия, такие, например, как создание нового объекта или его вычерчивание на экране. Если эти методы сделать виртуальными и инкапсулировать в объект-предок, каждый из его потомков сможет осуществить нужные действия одинаковым способом.
Для нашего примера можно создать следующий объект-родитель:
type
PGraphObject = TGraphObject;
TGraphObject = object (TObject)
X, Y: Integer; {Координаты характерной точки}
Constructor Init; {Создание объекта}
Procedure Draw; Virtual; {Вычерчивание}
end;
Объект TGraphObject содержит общие для всех потомков поля и методы. Заметим, что методы Init и Draw должны перекрываться в объектах-потомках, поэтому их содержимое не имеет значения. Однако полезно вынести в них некоторые общие для всех потомков части программы. Например, конструктор Init может помещать в поля X и Y заданные начальные значения; если этот метод наполнить конкретным содержанием, он может использоваться во всех объектах иерархии:
Constructor TGraphObject.Init;
{Присваивает случайные значения координатам X и Y}
begin
X := Random(GetMaxX);
Y := Random(GetMaxY)
end;
Здесь GetMaxX, GetMaxY - максимальные координаты графического экрана. Виртуальный метод Draw весьма специфичен: его конкретная программная реализация будет существенно зависеть от типа объекта. Поэтому объявим этот метод абстрактным:
Procedure TGraphObject.Draw;
{Абстрактный метод для вычерчивания графического примитива}
begin
Abstract
end;
Как видим, тело этого метода содержит обращение к глобальной процедуре Abstract, которая аварийно завершает выполнение программы и выдает соответствующую диагностику, если в программе используется вызов метода TGraphObject.Draw. Таким стандартным способом Turbo Vision сообщает пользователю о некорректности программы. Вы можете сделать тело этого метода другим, если Вас не устраивают стандартные действия, однако во всех случаях имеет смысл предусмотреть возможность некорректного вызова абстрактного метода, даже если вновь создаваемая библиотека будет использоваться только Вами - это значительно облегчит отладку программы.
Создадим три потомка от TGraphObject:
type
PPoint =TPoint;
TPoint = object (TGraphObject) {Точка}
Procedure Draw; Virtual;
end;
PCircle = TCircle; {Окружность}
TCircle = object (TGraphObject)
R: Integer;
Constructor Init;
Procedure Draw; Virtual;
end;
PRectangle = TRectangle; {Прямоугольник}
TRectangle = object (TGraphObject)
W, H: Integer;
Constructor Init;
Procedure Draw; Virtual;
end;
Объект TPoint (точка) не имеет новых полей и поэтому лишь перекрывает абстрактный метод TGraphObject.Draw:
Procedure TPoint.Draw; {Выводит точку на экран}
begin
PutPixel (X, Y, White)
end;
В объектах TCircle (окружность) и TRectangle (прямоугольник) инкапсулированы новые поля, поэтому перекрываются также и методы Init:
Constructor TCircle.Init;
{Создает окружность случайного радиуса в случайном месте}
begin
TGraphObject.Init; {Получаем координаты центра}
R := Random(GetMaxY div 2) {Получаем радиус}
end;
Procedure TCircle.Draw;
{Вычерчивает окружность}
begin
Circle(X, Y, R)
end;
Constructor TRectangle.init;
{Создает случайный прямоугольник}
begin
TGraphObject.Init;{Верхний левый угол}
W := Random(GetMaxX div 2) {Ширина}
H := Random(GetMaxY div 2) {Высота}
end;
Procedure TRectangle.Draw;
{Вычерчивает прямоугольник}
begin
Rectangle(X, Y, X+W, Y+H)
end;
После того как определены нужные объекты, не составляет особого труда поместить эти объекты в коллекцию и вывести их на экран. Например, для вывода всех элементов коллекции можно использовать такую процедуру:
Procedure DrawAll (C: PCollection) ;
{Выводит все элементы полиморфной коллекции}
Procedure DrawItem(p: PGraphObject); far;
begin
p.Draw {Это и есть полиморфизм в действии!}
end;
begin
С.ForEach(@DrawItem)
end;
Как видим, в процедуре DrawItem полиморфизм используется дважды: во-первых, метод ForEach обращается к ней, передавая в качестве параметра обращения нетипизированный указатель на элемент коллекции; это позволяет трактовать параметр как указатель на любой объект, в том числе и на TGraphObject. Во-вторых, в процедуре используется обращение к виртуальному методу объекта-родителя Draw: поскольку этот метод перекрывается во всех потомках, каждый из них будет использовать свой метод Draw для вывода на экран.
Сформируем программу, поместив в нее вместо, точек уже рассмотренные фрагменты:
Uses Objects,Graph,CRT;
type
.....
Constructor TGraphObject.Init;
.....
Procedure TGraphObject.Draw;
.....
Constructor TPoint.Init;
.....
Procedure TPoint.Draw;
.....
Constructor TCircle.Init;
.....
Procedure TCircle.Draw;
.....
Constructor TRectangle.Init;
.....
Procedure TRectangle.Draw;
.....
Procedure DrawAll(C: PCollection);
.....
var
a, r, k: Integer;
List: PCollection;
p: Pointer;
begin . a := 0;
{Инициируем графический режим работы экрана:}
InitGraph(a, r, '\TP\BGI');
r := GraphResult; if r <> 0 then
WriteLn(GraphErrorMsg(r)) {Ошибка инициации}
else
begin
{Создаем коллекцию:}
List := New(PCollection, Init (20,5));
{Наполняем ее 20 элементами:}
for k := 1 to 20 do
begin
case k mod 3 of
0: p := New(PPoint, Init);
1: p := New(PCircle, Init);
2: p := New(PRectangle, Init)
end;
if p <> NIL then List.Insert(p)
end ;
DrawAll(List) ; {Выводим на экран все элементы}
While not KeyPressed do;{Ждем нажатия на любую клавишу}
CloseGraph {Возвращаемся в текстовый режим}
end
end.
В этой программе предполагается, что драйвер графического экрана расположен в каталоге \TF\BGI на текущем диске. Если это не так, следует указать маршрут поиска
этого драйвера в качестве параметра обращения к процедуре InitGraph. Кроме того, каталог, содержащий стандартную графическую библиотеку Graph, должен быть указан опцией Options/Directories/Unit directories, если, разумеется, библиотека не содержится в текущем каталоге.