MarcosCunhaLima
MarcosCunhaLima

Reputation: 301

How to design a single interface to save different Collections in Delphi?

I have multiple collections like:

TFooList = TObjectDictionary<string,TFoo>;
TBarList = TObjectDictionary<string,TBar>;
....

TRoot = class
   value : string
end;

TFoo = class(TRoot)
...
end;

TBar = class(TRoot)
...
end;

And I have an interface/class that could save or load collections:

ISave = interface
  procedure Save( TDictionary<string, string> );
  function Load: TDictionary<string, string>;
end;

Note that the interface expects a key/string pair collection in order to work properly.

I implemented some ISave classes in order to load/save the collection to/from file or databases:

TDbSave = class( TInterfacedObject , ISave )
   ....
end; 
iSave := TDbSave( ConnString )

TFileSave = class( TInterfacedObject , ISave )
   ....
end; 
iSave := TFileSave( fileName );

So, the last piece would be inherit from each collection and create the save/load methods to "translate" each collection into/from TDictionary(string, string)

    TFooListSavable = TFooList;
      procedure Create( save_load : ISave );
      procedure Save;
      procedure Load;
      ....
   end;

   procedure TFooListSavable.Save
      // 1. create a  TDictionary<string, string>
      // 2. load the dictionary above with my collection translating
      //    each Foo object into a string
      // 3. call save_load.Save( dictionary );
   end;    
   procedure TFooListSavable.Load
      // 1. create a  TDictionary<string, string>
      // 2. call save_load.load to load it 
      // 3. Move over the collection and translate string into TFoo and
      // 4. AddOrEquals each TFoo created into TFooListSavable.
   end; 

So, I have two problems with this approach:

1) The interface that save or load expects a string value from the Collection and, although all the objects in each collection inherit from a class that has this string defined, I don't know how to transform a collection like TDictionary<string,TFoo> into a TDictionary<string,string> without resorting to the code above (which will duplicate the collection in order to pass it to iSave object).

2) I feel that, although I can replace iSave objects changing the way the collections would be saved/loaded without changing the collections themselves, I don't know if it is the best approach to save/load collections that keep related objects.

Upvotes: 2

Views: 378

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597215

I think you are going about this the wrong way.

ISave should not have any concept of any TDictionary at all. It should just expose methods for reading/writing basic data types (integers, strings, etc). Let TFooListSavable and TBarListSavable decide how to serialize their respective TDictionary data however they want, calling the ISave methods as needed.

Even better would be if TFooListSavable and TBarListSavable pass ISave to each individual TFoo/TBar and let them serialize their own data members directly.

For example, something like this:

type
  ISerialize = interface
    function HasData: Boolean;
    procedure StartWriteCollection;
    procedure StartWriteItem;
    procedure FinishWriteCollection;
    procedure FInishWriteItem;
    procedure WriteBoolean(value: Boolean);
    procedure WriteInteger(value: Integer);
    procedure WriteString(const value: String);
    ...
    procedure StartReadCollection;
    procedure StartReadItem;
    procedure FinishReadCollection;
    procedure FinishReadItem;
    function ReadBoolean: Boolean;
    function ReadInteger: Integer;
    function ReadString: String;
    ...
  end;

  TRoot = class
  public
    value : string;
    constructor Create; virtual;
    procedure Save(Dest: ISerialize); virtual;
    procedure Load(Src: ISerialize); virtual;
  end;

  TBaseList<T: TRoot, constructor> = class(TObjectDictionary<string, T>)
  public
    procedure Save(Dest: ISerialize);
    procedure Load(Src: ISerialize);
  end;

  TFoo = class(TRoot)
  public
    myint: Integer;
    ...
    procedure Save(Dest: ISerialize); override;
    procedure Load(Src: ISerialize); override;
  end;
  TFooList = TBaseList<TFoo>;

  TBar = class(TRoot)
    mybool: Boolean;
    ...
    procedure Save(Dest: ISerialize); override;
    procedure Load(Src: ISerialize); override;
  end;
  TBarList = TBaseList<TBar>;

  TDbSerialize = class(TInterfacedObject, ISerialize)
    ...
  end; 

  TFileSerialize = class(TInterfacedObject, ISerialize)
    ...
  end; 

  procedure TBaseList<T>.Save(Dest: ISerialize);
  var
    pair: TPair<string, T>;
  begin
    Dest.StartWriteCollection;
    for pair in Self do
    begin
      Dest.StartWriteItem;
      Dest.WriteString(pair.Key);
      TRoot(pair.Value).Save(Dest);
      Dest.FinishWriteItem;
    end;
    Dest.FinishWriteCollection;
  end;

  procedure TBaseList<T>.Load(Src: ISerialize);
  var
    Cnt, I: Integer;
    key: string;
    value: T;
  begin
    Self.Clear;
    Src.StartReadCollection;
    While Src.HasData do
    begin
      Src.StartReadItem;
      key := Src.ReadString;
      value := T.Create;
      try
        value.Load(Src);
        Self.Add(key, value);
      except
        value.Free;
        raise;
      end;
      Src.FinishReadItem;
    end;
    Src.FinishReadCollection;
  end;

  procedure TRoot.Save(Dest: ISerialize);
  begin
    Dest.WriteString(value);
  end;

  procedure TRoot.Load(Src: ISerialize);
  begin
    value := Src.ReadString;
  end;

  procedure TFoo.Save(Dest: ISerialize);
  begin
    inherited;
    Dest.WriteInteger(myint);
  end;

  procedure TFoo.Load(Src: ISerialize);
  begin
    inherited;
    myint := Src.ReadInteger;
  end;

  procedure TBar.Save(Dest: ISerialize);
  begin
    inherited;
    Dest.WriteBoolean(mybool);
  end;

  procedure TBar.Load(Src: ISerialize);
  begin
    inherited;
    mybool := Src.ReadBoolean;
  end;

Upvotes: 2

Related Questions