Очень часто при разборе чужого кода сталкиваюсь с непониманием программистов разницы между динамическими и виртуальными методами. Лепят везде virtual не задумываясь. Даже определение придумали "завиртуалить и перекрыть". Давайте попробуем разобраться в чем же разница.
Как известно, главными принципами объектно-ориентированного программирования является наследование и полиморфизм (есть еще инкапсуляция, но нам сейчас этот принцип не интересен). Это значит, что от любого объекта можно создать наследника и в этом наследнике переопределить методы родительского класса. Пример:
type
TMyFirstClass = class
protected
procedure AnyMethod; virtual; {или dynamic}
end;
TMySecondClass = class(TMyFirstClass)
protected
procedure AnyMethod; override;
end;
Что мы тут видим? TMyFirstClass и его наследника TMySecondClass. У обоих есть метод с именем AnyMethod. У TMyFirstClass он объявлен как virtual (но возможно объявить как dynamic. В чем разница мы как раз и разбираем). У TMySecondClass этот метод объявлен как override, что говорит нам о том, что этот метод перекрывает родительский. Теперь, если мы создадим объект TMyFirstClass и вызовем AnyMethod, то выполнится метод класса TMyFirstClass. Если же объект будет создан как TMySecondClass, то выполнится метод класса TMySecondClass. Пример:
procedure TestAnyMethod; var MyClass: TMyFirstClass; begin MyClass := TMyFirstClass.Create // создаем объект класса TMyFirstClass MyClass.AnyMethod; // выполняем метод класса TMyFirstClass MyClass.Free; // освобождаем объект MyClass := TMySecondClass.Create // создаем объект класса TMySecondClass MyClass.AnyMethod; // выполняем метод класса TMySecondClass MyClass.Free; // освобождаем объект end;
Как видим, оба блока отличаются только указанием класса, от которого создается MyClass, но из-за этого метод AnyMethod фактически вызывает разный код.
С теорией разобрались, теперь вернемся к нашему вопросу, в чем же разница между virtual и dynamic?
Так вот, когда мы объявляем в родительском классе метод как virtual, при создании потомка этого класса для него (потомка) так же будет создан этот метод в таблице методов в памяти. Это хорошо если данный метод перекрыт в потомке и нам в любом случае он необходим в памяти. А если метод не перекрыт? Зачем нам идентичная копия метода? А если у нас реализована цепочка наследования из 10 классов и только в одном из наследников этот метод перекрыт, то зачем нам создавать 10 копий одного и того же? Проще говоря, при объявлении в классе метода как virtual, в каждом наследнике этого класса будет создана копия этого метода вне зависимости от того перекрыт этот метод или нет. Что нам это дает? Скорость вызова. Каждый класс имеет свою копию метода и может быстро его вызвать, не тревожа при этом своего родителя, родителя своего родителя и так далее по цепочке. Минус - занимаемая память.
Если же мы объявим в родительском классе метод как dynamic, то он не будет создаваться в наследниках, если наследники его не перекрывают. Таким образом, даже если у нас реализована цепочка наследования из 10 классов, такой метод будет в памяти только в одном экземпляре. Что нам это дает? Экономия памяти. Минус - скорость вызова. Дело в том, что если мы пытаемся вызвать такой метод у наследника, он пытается найти этот метод в своей таблице методов - не находит. Пытается найти этот метод в таблице методов своего родителя - не находит. И так далее по цепочке наследования. На это тратится время.
Что мы имеем в итоге?
- Если у вас длинная цепочка наследования и метод, который редко вызывается, используйте dynamic. Этим вы сэкономите память в ущерб скорости в вызова, что на редко вызываемых методах будет не существенно. Именно по этой причине большинство методов в VCL объявлены как dynamic.
- Если у вас длинная цепочка наследования и метод, который вызывается часто, оценивайте необходимость быстрого вызова. Если это не критично, используйте dynamic. Если быстрый вызов важен несмотря на занимаемую память, используйте virtual.
- Если у вас не большая цепочка наследования, используйте virtual. Экономия памяти при использовании dynamic в таком случае будет минимальна.
Другими словами, dynamic - метод создается в памяти один раз, но для его доступа необходимо пройти по всей цепочке наследования. virtual - метод копируется для каждого наследника. Если вам не важна используемая программой память, но очень важна скорость выполнения методов - virtual. Если скорость не существенна, а вот память хочется сэкономить - dynamic.
Комментариев нет:
Отправить комментарий