Шаг 76.
Решение задачи. Создание объектов. Таблица виртуальных методов

    Этот шаг посвящен вопросам перекрытия методов родительских объектов.

    Процедура Draw предназначена для вычерчивания графического объекта. Эта процедура будет реализовываться в потомках объекта TGraphObj по-разному. Например, для визуализации точки следует вызвать процедуру PutPixel, для вычерчивания линии - процедуру Line и т.д. В объекте TGraphObj процедура Draw определена как виртуальная ("воображаемая") с помощью директивы Virtual (см.предыдущий шаг). Абстрактный объект 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 и визуализация - стирание объекта осуществлялась бы с помощью этого метода.

   

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

Type
     TGraphObj = Оbject
          .  .  .  .  .
     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 переменные в левых частях операторов присваивания представляют собой объектные поля и не должны заново описываться в процедуре.

    На следующем шаге рассмотрим создание объектов-потомков.


Предыдущий шаг Содержание Следующий шаг