Используйте виртуальный конструктор для сброса в исходное состояние

У меня нет опыта работы с виртуальными конструкторами, которые доступны в 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 на ссылку этого класса. Это вызывает виртуальный конструктор для фактического класса, и он заканчивается экземпляром компонента этого типа и начинает заполнять его свойства.

Другие вопросы по тегам