четверг, 11 сентября 2014 г.

Коротко. Ещё немного "рассуждений о RAII"

По мотивам - 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'ы. Похоже, что всерьёз и надолго.

Комментариев нет:

Отправить комментарий