В комментариях было поднято несколько вопросов, которые я хотел бы уточнить.
Первый - это продолжающийся миф о том, что конструктор должен быть виртуальным. Это не . Рассмотрим этот пример:
type
TBase = class
constructor Create(x: Integer);
end;
TDerived = class(TBase)
field: string;
end;
TMetaclass = class of TBase;
var
instance: TBase;
desiredClass: TMetaclass;
begin
desiredClass := TDerived;
instance := desiredClass.Create(23);
Assert(instance.ClassName = 'TDerived');
Assert(instance is TDerived);
Assert(instance.field = '');
end;
Созданный объект будет полноценным экземпляром класса TDerived
. Будет выделено достаточно памяти для хранения строкового поля, которого не было в базовом классе.
Есть два условия, которые должны быть выполнены, прежде чем вам понадобится виртуальный конструктор:
- Конструктор будет вызываться виртуально. То есть у вас будет переменная типа метакласса базового класса, и она будет содержать значение производного класса, и вы вызовете конструктор для этой переменной. Это продемонстрировано в коде выше. Если все ваши вызовы конструктора находятся непосредственно в самих именах классов (т. Е.
TDerived.Create(23)
), то ничего нельзя получить от виртуальных методов.
- Подкласс базового класса должен будет переопределить конструктор, чтобы обеспечить специфическую для класса инициализацию. Если все потомки используют одну и ту же конструкцию и различаются только другими методами, десять не нужно делать конструктор виртуальным.
Здесь важно понимать, что эти два правила ничем не отличаются от факторов, определяющих, когда любой другой метод становится виртуальным. Конструкторы не являются особенными в этом отношении.
Конструктор знает, какой класс создавать, основываясь не на классе, в котором был определен конструктор, а на том классе, к которому был вызван конструктор, и этот класс всегда передается как скрытый первый параметр для каждого вызова конструктора.
Вторым является вопрос о том, должен ли NewInstance
вызываться вместо конструктора или в дополнение к нему. Я думаю, что другие комментарии уже установили, что это не имеет ничего общего с совместимостью со старыми версиями Delphi. Все версии поддерживают вызов конструкторов для ссылок на классы без необходимости NewInstace
. Скорее путаница возникает из-за того, что вы смотрите на TApplication.CreateForm
и рассматриваете его как пример того, как все должно быть сделано. Это ошибка.
CreateForm
вызывает NewInstance
перед вызовом конструктора, поскольку основная причина существования CreateForm
заключается в том, чтобы убедиться, что глобальная переменная формы, которую объявляет IDE, действительна во время собственных обработчиков событий формы, включая OnCreate
, который работает как часть конструктора. Если бы метод CreateForm
выполнил обычный шаблон построения, тогда глобальная переменная формы еще не имела бы допустимого значения. Вот что вы могли ожидать увидеть:
TComponent(Reference) := InstanceClass.Create(Application);
Просто и очевидно, но это не сработает. Reference
не будет присвоено значение до тех пор, пока после не вернется конструктор, то есть спустя много времени после того, как форма вызовет некоторые события. Если вы следуете хорошей практике программирования и никогда не ссылаетесь на эту переменную из самого класса формы, то вы никогда не заметите. Но если вы будете следовать инструкциям документации, написанным для неопытной аудитории, то вы будете ссылаться на глобальную переменную формы из собственных методов формы, поэтому метод CreateForm
делает то, что он может сделать уверен, что он назначен вовремя.
Для этого используется двухступенчатая методика строительства. Сначала выделите память и назначьте ссылку на глобальную переменную:
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
Затем вызовите конструктор экземпляра, передав объект TApplication
в качестве владельца:
Instance.Create(Self);
По моему мнению , CreateForm
следует вызывать ровно один раз в любой программе. Я бы предпочел ноль раз, но у него есть побочный эффект определения Application.MainForm
, что важно для других аспектов программы Delphi.
В-третьих, понятие, что для объекта необычно вызывать конструктор для себя.
На самом деле, это происходит все время . Каждый раз, когда вы вызываете унаследованный конструктор, вы вызываете конструктор для уже существующего объекта. Унаследованный конструктор не выделяет новый объект. Аналогично, в VCL есть несколько примеров ненаследуемых вызовов конструкторов. TCustomForm.Create
делегирует большую часть задач построения конструктору CreateNew
.