Используйте виртуальный конструктор для сброса в исходное состояние
У меня нет опыта работы с виртуальными конструкторами, которые доступны в Delphi. Я считаю использование виртуальных ctors в иерархии классов для сброса экземпляра в начальное состояние, как это:
A = class
end;
B = class(A)
end;
C = class(B)
end;
FooA = class
a_ : A;
constructor Create(inst : A); overload;
constructor Create; overload; virtual; abstract;
destructor Destroy; override;
function Bar : A;
end;
FooB = class(FooA)
b_ : B;
constructor Create; override;
constructor Create(inst : B); overload;
end;
FooC = class(FooB)
// ...
end;
{ FooA }
constructor FooA.Create(inst: A);
begin
inherited Create;
a_ := inst;
end;
destructor FooA.Destroy;
begin
FreeAndNil(a_);
inherited;
end;
function FooA.Bar : A;
begin
Result := a_;
a_ := nil;
// here comes the magic
Self.Create;
end;
{ FooB }
constructor FooB.Create;
begin
b_ := B.Create;
inherited Create(b_);
end;
constructor FooB.Create(inst: B);
begin
inherited Create(inst);
b_ := inst;
end;
{ FooC } // ...
var
fc : FooA;
baz : A;
begin
fc := FooC.Create;
baz := fc.Bar;
WriteLn(baz.ClassName);
FreeAndNil(baz);
FreeAndNil(fc);
ReadLn;
end.
Есть ли проблемы / подводные камни в этом дизайне? Простой пример работает как шарм, но я чувствую себя немного неловко, вызывая конструкторов (которые ничего не конструируют), как это.
Редактировать:
Я решил перенести инициализацию на метод в защищенной области со значимым именем, что заставляет меня чувствовать себя лучше;-)
FooA = class
strict private
a_ : A;
strict protected
procedure SetInst; overload; virtual; abstract;
procedure SetInst(i : A); overload;
public
constructor Create;
destructor Destroy; override;
function Foo : A;
end;
2 ответа
Очень мало классов написано для поддержки использования конструкторов в качестве реинициализаторов. Обычно они предполагают, что любая динамически распределенная память еще не была выделена. Если вы контролируете все используемые вами классы, тогда продолжайте и осторожно используйте конструкторы в качестве реинициализаторов.
Даже если вы контролируете ситуацию, я все равно советую против этого. Это не идиоматичный Delphi; любой, кто читает ваш код (возможно, даже вы, через несколько недель или месяцев), будет смущен - по крайней мере, на первый взгляд - вашим нестандартным использованием конструкторов. Это не стоит хлопот. Если звонит Bar
функция должна освободить право собственности на A
Возьмите объект и создайте новый экземпляр, затем напишите функции с именами, которые прояснят это.
Роб прав в том, что это действительно странно выглядящий код, который может сбить людей с толку, и перевод вашего кода в процедуру инициализации - хорошая идея. Если вам интересно, основная цель виртуальных конструкторов - это нечто совершенно иное: более легкая поддержка создания объектов в "заводском" стиле.
Некоторые внешние источники предоставляют некоторые данные, которые могут идентифицировать любого потомка базового класса, а фабрика использует ссылку на класс и вызывает для нее виртуальный конструктор, определенный в базовом классе. Таким образом, вы получите экземпляр класса-потомка без необходимости жесткого кодирования знания класса-потомка в код фабрики.
Если это звучит немного странно, взгляните на файл DFM. У него есть список объектов формы, которые происходят от TComponent, с их опубликованными свойствами. Когда код чтения формы сталкивается с object
оператор, он читает имя класса, ищет его в таблице, которая отображает имена классов на ссылки на классы, и вызывает виртуальный TComponent.Create
на ссылку этого класса. Это вызывает виртуальный конструктор для фактического класса, и он заканчивается экземпляром компонента этого типа и начинает заполнять его свойства.