По мотивам - RAII.
Хочется написать об "интегральных объектах".
Т.е. о тех, которые внутри себя создают другие объекты.
Обычно делается так:
Но иногда можно сделать так:
В чём цимес?
А в том, что экземпляр TSomeClass не создастся до того как создадутся экземпляры TSomeClass1 и TSomeClass2.
А значит и деструктор не вызовется.
И соответственно - SomeInitCode - тоже не вызовется.
А значит и проблем с уничтожением частично созданного объекта - не будет.
С SomeDoneCode - "есть вопросы". Оно вызовется (читаем документацию).
Но!
Если оно зависит только от объектов инициализированных выше, то проблем тоже не будет.
При этом оно вызовется только если строки:
- выполнятся.
А строки:
- почему-то не пройдут.
Но там - "вероятность стремится к нулю".
Ну можно написать как-то так:
Или даже так (совсем для параноиков как я):
- так наверное - понятнее?
Ну и TSomeClass1.Create и TSomeClass2.Create - можно сделать так же "по индукции", если они тоже - интегральные.
Ну и не забываем о том (что тоже написано в документации), что если конструктор кидает исключение, то обязательно вызывается деструктор. Пусть даже и на "частично инициализированном объекте".
Повторю ещё раз:
Если конструктор кидает исключение, то обязательно вызывается деструктор. Пусть даже и на "частично инициализированном объекте".
Это - надо иметь в виду.
Именно поэтому у меня был такой пост - Коротко. Заметки о рефакторинге
К чему я?
А к тому:
Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "частично инициализированном объекте".
Повторю:
Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "частично инициализированном объекте".
Т.е. чем меньше делается именно в конструкторе, а не в "фабрике". То - "тем спокойнее".
И тем "меньше вероятность" получить AccessViolation и/или неосвобождённые ресурсы.
Буду рад, если эти мысли кому-то окажутся полезны.
Я у себя их применил. И не раз. Но я же - не критерий.
Ну а если "вдруг интересно", то могу попробовать продолжить на реальном примере.
P.S. Что мы имеем при таком подходе, а то, что с одной стороны мы вроде инкапсулируем "внутреннюю логику" в классе. А с другой стороны - не даём создавать "частично инициализированный экземпляр класса".
В общем - я лично с некоторых пор полюбил фабрики, примеси и Assert'ы. Похоже, что всерьёз и надолго.
Хочется написать об "интегральных объектах".
Т.е. о тех, которые внутри себя создают другие объекты.
Обычно делается так:
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'ы. Похоже, что всерьёз и надолго.
Комментариев нет:
Отправить комментарий