среда, 18 марта 2015 г.

Briefly. Again about factories

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/blog-post_3.html

In a way, based on:

Briefly. About factories and http://programmingmindstream.blogspot.ru/2014/08/istorage-tdd.html

What do I want to talk about?

We have our own implementation of IStorage (and, therefore,  IStream).

It is good. In a sense. It is good at least because it works stable for already 15 years.

But there are “some problems”.

I examine these problems scrupulously now.

I do not speak about the “problems” of complex implementation in heterogeneous network environment.

There are “local problems”, too.

For example, the fact that everything is built there on "binary serialisation"  .

I.e. something like this:

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;

You can look at the “real code” here.

What’s the problem? The problem is the format of TStoreHeader cannot be changed “for no reason” – everything will “get displaced”.

What should we do?

To begin with, we do something like this:

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;

What’s next?

Something like this:

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;

What have we done here?

We’ve introduced a base abstract class - TStoreHeaderAbstract and a factory - TStoreHeaderFactory.

How can we change data format?

In this way:

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, 'Incorrect header');
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;

Moreover, we can also write in this way:

...
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;

What have we done here?

In the first place, we’ve divided “binary serialization” of a record into a few “binary serializations” of separate fields.

In the second place, we’ve reversed some of the fields in order to demonstrate “the heart of the approach”.

Lots of questions are left beyond the scope.

For example – “where do we get GetVersionGUID?”

Or – “what should we do if writing of version is not supported initially”?

These are important questions. But they “do not fit in the framework” of the post. Generally speaking, these are important, but more “technical questions”. If you are interested I will analyze them in detail.

For now I’ll leave them “beyond the scope”.

What is the result?

In sum, in my opinion, it has been shown that factories  are a weighty supplement to encapsulation  and polymorphism.

First we’ve used polymorphism - by introducing the type TStoreHeaderAbstract.
Then we’ve used encapsulation – by dividing TStoreHeader.Data and TStoreHeaderNew.Data.

Through polymorphism and encapsulation we’ve, in a way, avoided the “binary serialization”.

Why?
Because the next step could be the following:

...
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;

- i.e. here we no more write/read “binarily”, but as it is written in SomeOtherDataType.Load/SomeOtherDataType.Save.

So.

What I wanted to show?

I’ll repeat.

I wanted to show that factories are a weighty supplement to encapsulation and polymorphism.

(Let’s say, factories are “twice the polymorphism”. That is because polymorphism “starts to have effect” even before object instance is created. Then, factory polymorphism works. Should I write about polymorphic factories?)

You are to judge how much I succeeded in achieving my goal.

I guess I did not “reinvented the wheel”, but I hope I’ve written something of use.




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

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