По мотивам -
RAII.
Хочется написать об "интегральных объектах".
Т.е. о тех, которые
внутри себя создают
другие объекты.
Обычно делается так:
type
TSomeClass1 = class
public
constructor Create(aSomeData1 : TSomeType1);
end;//TSomeClass1
TSomeClass = class
private
f_SomeClass1 : TSomeClass1;
f_SomeClass2 : TSomeClass2;
public
constructor Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
destructor Destroy; override;
end;//TSomeClass
...
constructor TSomeClass1.Create(aSomeData1 : TSomeType1);
begin
Assert(IsValid(aSomeData1));
inherited Create;
...
end;
...
constructor TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
begin
inherited Create;
f_SomeClass1 := TSomeClass1.Create(aSomeData1);
SomeInitCode;
f_SomeClass2 := TSomeClass2.Create(aSomeData2);
end;
destructor TSomeClass.Destroy;
begin
FreeAndNil(f_SomeClass2);
SomeDoneCode;
FreeAndNil(f_SomeClass1);
inherited;
end;
Но иногда можно сделать так:
type
TSomeClass = class
private
f_SomeClass1 : TSomeClass1;
f_SomeClass2 : TSomeClass2;
protected
constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
public
class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
destructor Destroy; override;
end;//TSomeClass
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
inherited Create;
f_SomeClass1 := aSomeClass1;
SomeInitCode;
f_SomeClass2 := aSomeClass2;
end;
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
l_SomeClass1: TSomeClass1;
l_SomeClass2: TSomeClass2;
begin
Assert(IsValid(aSomeData1));
Assert(IsValid(aSomeData2));
l_SomeClass1 := TSomeClass1.Create(aSomeData1);
l_SomeClass2 := TSomeClass2.Create(aSomeData2);
Result := Make(l_SomeClass1, l_SomeClass2);
end;
destructor TSomeClass.Destroy;
begin
FreeAndNil(f_SomeClass2);
SomeDoneCode;
FreeAndNil(f_SomeClass1);
inherited;
end;
В чём цимес?
А в том, что экземпляр TSomeClass не создастся
до того как создадутся экземпляры TSomeClass1 и TSomeClass2.
А значит и деструктор
не вызовется.
И соответственно - SomeInitCode - тоже не вызовется.
А значит и проблем с уничтожением
частично созданного объекта -
не будет.
С SomeDoneCode - "есть вопросы". Оно вызовется (читаем документацию).
Но!
Если оно зависит только от объектов
инициализированных выше, то проблем
тоже не будет.
При этом оно вызовется только если строки:
...
l_SomeClass1 := TSomeClass1.Create(aSomeData1);
l_SomeClass2 := TSomeClass2.Create(aSomeData2);
...
-
выполнятся.
А строки:
...
inherited Create;
f_SomeClass1 := aSomeClass1;
...
- почему-то не пройдут.
Но там - "вероятность стремится к нулю".
Ну можно написать как-то так:
type
TSomeClass = class
private
f_SomeClass1 : TSomeClass1;
f_SomeClass2 : TSomeClass2;
protected
constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
public
class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
destructor Destroy; override;
end;//TSomeClass
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
inherited Create;
f_SomeClass1 := aSomeClass1;
SomeInitCode;
f_SomeClass2 := aSomeClass2;
end;
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
l_SomeClass1: TSomeClass1;
l_SomeClass2: TSomeClass2;
begin
l_SomeClass1 := TSomeClass1.Create(aSomeData1);
l_SomeClass2 := TSomeClass2.Create(aSomeData2);
Result := Make(l_SomeClass1, l_SomeClass2);
end;
destructor TSomeClass.Destroy;
begin
FreeAndNil(f_SomeClass2);
if (f_SomeClass1 <> nil) then
// - проверяем, что f_SomeClass1 - инициализирован
// Почему такая проверка? А ответ такой - "а иначе зачем этот SomeDoneCode именно в ЭТОМ МЕСТЕ?
SomeDoneCode;
FreeAndNil(f_SomeClass1);
inherited;
end;
Или даже так (совсем для параноиков как я):
type
TSomeClass = class
private
f_SomeClass1 : TSomeClass1;
f_SomeClass2 : TSomeClass2;
protected
constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
public
class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
destructor Destroy; override;
end;//TSomeClass
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
inherited Create;
f_SomeClass1 := aSomeClass1;
SomeInitCode(f_SomeClass1);
f_SomeClass2 := aSomeClass2;
end;
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
l_SomeClass1: TSomeClass1;
l_SomeClass2: TSomeClass2;
begin
l_SomeClass1 := TSomeClass1.Create(aSomeData1);
l_SomeClass2 := TSomeClass2.Create(aSomeData2);
Result := Make(l_SomeClass1, l_SomeClass2);
end;
destructor TSomeClass.Destroy;
begin
FreeAndNil(f_SomeClass2);
if (f_SomeClass1 <> nil) then
// - проверяем, что f_SomeClass1 - инициализирован
// Почему такая проверка? А ответ такой - "а иначе зачем этот SomeDoneCode именно в ЭТОМ МЕСТЕ?
SomeDoneCode(f_SomeClass1);
FreeAndNil(f_SomeClass1);
inherited;
end;
- так наверное - понятнее?
Ну и TSomeClass1.Create и TSomeClass2.Create - можно сделать так же "по индукции", если они тоже -
интегральные.
Ну и не забываем о том (что тоже написано в документации), что если конструктор кидает исключение, то
обязательно вызывается деструктор. Пусть даже и на "частично инициализированном объекте".
Повторю
ещё раз:
Если конструктор кидает исключение, то
обязательно вызывается деструктор. Пусть даже и на "частично инициализированном объекте".
Это - надо
иметь в виду.
Именно поэтому у меня был такой пост -
Коротко. Заметки о рефакторинге
К чему я?
А к тому:
Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "
частично инициализированном объекте".
Повторю:
Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "
частично инициализированном объекте".
Т.е. чем меньше делается
именно в конструкторе, а не в "фабрике". То - "тем спокойнее".
И тем "меньше вероятность" получить AccessViolation и/или неосвобождённые ресурсы.
Буду рад, если эти мысли кому-то окажутся полезны.
Я у себя их применил. И не раз. Но я же - не критерий.
Ну а если "вдруг интересно", то могу попробовать продолжить на реальном примере.
P.S. Что мы имеем при таком подходе, а то, что с одной стороны мы вроде инкапсулируем "внутреннюю логику" в классе. А с другой стороны - не даём создавать "частично инициализированный экземпляр класса".
В общем - я лично с некоторых пор полюбил фабрики, примеси и Assert'ы. Похоже, что всерьёз и надолго.