Этот шаг посвящен вопросам перекрытия методов родительских объектов.
Процедура 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 переменные в левых частях операторов присваивания
представляют собой объектные поля и не должны заново описываться в процедуре.
На следующем шаге рассмотрим создание объектов-потомков.