среда, 3 сентября 2014 г.

Коротко. Ещё о фабриках

В некотором смысле по мотивам:

Коротко. О фабриках
Маниловщина. Пишем реализацию IStorage применяя TDD

О чём хочу повести речь?

У нас своя есть реализация IStorage (ну и IStream соответственно).

Она - хороша. В некотором смысле. Хотя бы потому, что достаточно стабильно работает уже лет 15-ть.

Но там есть "некоторые проблемы".

И я с этими проблемами сейчас скурпулёзно разбираюсь.

Ну там "проблемы" сложного порядка в гетерогенной сетевой среде.

Я не о них.

Но есть и "локальные проблемы".

Например то, что там всё построено на "бинарной сериализации".

Т.е. сделано примерно так:

type
 TStoreHeader = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeader

...

procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Read(l_H, SizeOf(l_H);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Write(l_H, SizeOf(l_H);
 ...
end;

"Реальный код" можно посмотреть тут.

В чём проблема? А в том, что "формат" TStoreHeader "просто так" не изменишь. Потому, что "всё поедет".

Что делать?

Для начала вот примерно так:

type
 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream);
   procedure Save(aStream: TStream);
 end;//TStoreHeader

...

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Save(Stream);
 ...
end;

Какой следующий шаг?

Вот как-то так:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract

...

 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader

 TStoreHeaderFactory = class
  public
   class function Make: TStoreHeaderAbstract;
 end;//TStoreHeaderFactory

...

class function TStoreHeaderFactory.Make: TStoreHeaderAbstract;
begin
 Result := TStoreHeader.Create;
end;

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Save(Stream);
 ...
end;

Что мы тут сделали?

Мы ввели базовый абстрактный класс - TStoreHeaderAbstract и фабрику -TStoreHeaderFactory.

Теперь как нам поменять формат данных?

А вот примерно так:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract

...

 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader

 TStoreHeaderRecNew = record
  rNextPosition : Int64;
  rRealSize : Int64;
  rSomeOtherData : SomeOtherType;
  ...
 end;//TStoreHeaderRecNew

 TStoreHeaderNew = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRecNew;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeaderNew

 TStoreHeaderFactory = class
  public
   class function Make(aVersion : TGUID): TStoreHeaderAbstract;
 end;//TStoreHeaderFactory

...

class function TStoreHeaderFactory.Make(aVersion : TGUID): TStoreHeaderAbstract;
begin
 if EqualGUID(aVersion, OldFormatGUID) then
  Result := TStoreHeader.Create
 else
 if EqualGUID(aVersion, NewFormatGUID) then
  Result := TStoreHeaderNew.Create
 else
  Assert(false, 'Неверный заголовок');
end;

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Save(Stream);
 ...
end;

Более того можно тогда написать и так:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.ReadBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.WriteBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;

Что мы тут сделали?

Мы во-первых - разбили "бинарную сериализацию" записи на несколько "бинарных сериализаций" отдельных полей.

А во-вторых - мы поменяли часть полей местами, чтобы продемонстрировать "суть подхода".

За рамками повествования конечно осталось много вопросов.

Например - "откуда берётся GetVersionGUID"?

Или - "что делать, если запись версии изначально не предусмотрены"?

Это важные вопросы. Но они "не влезают в рамки" данного поста. Да и вообще говоря - они важные, но "достаточно технические". Если будет интерес - я и их подробнее разберу.

Но пока - оставлю их "за рамками повествования".

Что в итоге?

В  итоге - по-моему - было показано как фабрики являются весомым дополнением к инкапсуляции и полиморфизму.

Мы сначала воспользовались полиморфизмом - введя тип TStoreHeaderAbstract.
А потом воспользовались инкапсуляцией - разделив TStoreHeader.Data и TStoreHeaderNew.Data.

Ну и за счёт полиморфиза и инкапсуляции мы в некотором роде ушли от "бинарной сериализации".

Почему?

Потому, что следующий шаг может быть таким:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Load(aStream);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Save(aStream);
end;

- т.е. тут уже пишем/читаем не "бинарно", а так как написано в SomeOtherDataType.Load/SomeOtherDataType.Save.

Итак.

Что я хотел показать?

Я повторю.

Я хотел показать, что фабрики являются весомым дополнением к инкапсуляции и полиморфизму.

(Но фабрики, скажем так - это "полиморфизм в квадрате". Потому, что полиморфизм "начинает действовать" ещё до создания экземпляра объекта, до создания экземпляра объекта может работать полиморфизм фабрики. О полиморфных фабриках стоит написать?)

Как уж это у меня получилось - судить вам.

Думаю - "Америку не открыл", но надеюсь, что написал что-то полезное.

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

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