Порядок вызова наследуемого метода. Конструктор.
Большая часть объектов Turbo Vision спроектирована в расчете на их дальнейшее перекрытие в прикладных программах. Типичным примером такого рода объектов является TView, метод Draw которого создает на экране пустой прямоугольник и, следовательно, не может отображать никакой полезной информации. Поскольку все видимые элементы порождены от TView, Вам необходимо перекрыть метод Draw в собственном объекте-потомке. Более того, поскольку TView.Draw не делает никакой полезной работы, его не нужно вызывать в перекрытом методе.
Однако полностью перекрываемые методы, подобные TView.Draw, скорее исключение из общего правила. Обычно в перекрытом методе вызывается соответствующий метод, наследуемый от родителя, т.к. в нем реализуются некоторые необходимые для потомка действия. В такого рода ситуациях важна последовательность вызова наследуемого метода: вызывать ли его до реализации специфичных действий или после? Ниже приводятся практические рекомендации на этот счет.
Вызывайте наследуемый Метод до реализации дополнительных действий:
Procedure MyObject.Init(…..); begin {Вызов наследуемого конструктора Init} {Реализация дополнительных действий} end;
Такая последовательность необходима по той простой причине, что вызов наследуемого конструктора приводит к обнулению всех дополнительных полей объекта MyObject. Если, например, Вы используете следующий фрагмент программы:
type MyObject = object (TWindow) Value: Word; Ok: Boolean; Constructor Init(var Bounds: TRect; ATitle: TTitleStr; AValue: Word; AOk: Boolean); end; Constructor MyObject.Init; begin Inherited Init(Bounds, ATitle, wnNoNumber); Value: = 16; Ok: = True; end;
То дополнительные поля Value и Ok получат нужные значения 16 и True. Однако, если обращение TWindow.Init (Bounds, ATitle, wnNoNumber); поставить после оператора Ok: = True, в них будут помещены значения 0 и False. Из этого правила существует одно исключение, связанное с загрузкой коллекции из потока конструктором Load. Дело в том, что в наследуемом методе TCollection.Load реализуется следующий цикл:
Constructor TCollection.Load (var S: TStream); begin ….. for I: = 0 to Count – 1 do AtPut(I, GetItem(S)); end;
Если элементами коллекции являются произвольные наборы двоичных данных (не объекты), Вам потребуется перед чтением очередного элемента сначала получить из потока его длину. Следующий пример иллюстрирует сказанное.
type PDataCollection = ATDataCollection; TDataCollection = object (TStringCollection) ItemSize: Word; Constructor Load(var S: TStream); Function GetItem(var S: TStream): Pointer; Virtual; ….. end; Constructor TDataCollection.Load(var S: TStream); begin S.Read(ItemSize, SizeOf(ItemSize)); Inherited Load(S); end; Function TDataCollection.GetItem(var S: TStream): Pointer; var Item: Pointer; begin GetMem(Item, ItemSize); S.Read(Item, ItemSize); GetItem: = Item; end;
В этом примере конструктор Load сначала загружает из потока поле ItemSize, содержащее длину читаемого элемента. Затем вызывается конструктор TCollection.Load, в котором осуществляется вызов GetItem. Новый GetItem использует поле ItemSize, чтобы определить размер читаемых данных, и резервирует нужный буфер в динамической памяти. Разумеется, запись полиморфных коллекций в поток должна происходить в том же порядке, т.е. сначала записывается длина очередного элемента, а уже потом – его данные.