30.09.2013

Динамические (dynamic) и виртуальные (virtual) методы.

Очень часто при разборе чужого кода сталкиваюсь с непониманием программистов разницы между динамическими и виртуальными методами. Лепят везде 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.

Комментариев нет:

Отправить комментарий