Прочитав кучу статей о том, как велико ООП и как здорово, что оно есть, решил что пора пользоваться этим всеми прелестями, которое оно нам несёт. Прежде чем изобретать велосипед внимательно были искурены форумы, которые содержали в себе слова: сериализация, XML и JSON. Всё было прекрасно, хорошо документировано, но ощущение чего то незаконченного не покидало…
Были пару робких попыток пристроить для своих целей XML или JSON, но сталкивался с тем что над реализацией хранения данных в этих форматах, в каждом новом проекте, уходило не мало времени на написание методов сохранения и загрузки. Всё таки не даёт XML (и ему подобные) того уровня абстракции который бы хотелось иметь в наличии…

1. Истина где-то рядом


Пока вёл свои изыскания с XML неоднократно наталкивался на практику применения RTTI в делфи. Но так и не прижилось… Разведения гетеров и сетеров для published свойств сильно засоряло код, хотя свои плюсы несомненно есть в этом подходе. Но смысл для не очень сложного проекта, с простой системой классов разводить весь этот огород? В дальнейшем я наткнулся на коллекции и в принципе они хорошо справляются с отведёнными для них задачами. Но… Не хватает универсальности использования и монотонность одних и тех же действий в разных проектах при работе с ними меня лично напрягало. Всё это в итоге сподвигло меня на написания своего велосипеда…
Итак, что я хотел или сама идея: есть некий сферический класс в вакууме, который имеет некоторые свойства и, соответственно, значения этих свойств:

image

Так же было бы здорово. Если бы был класс, который хранил множество «сферических» классов:

image

А уже вообще счастью не было бы предела, если бы создать такой класс, который мог бы сохранять класс-хранитель в файл, да ещё в разных форматах… Вот такая получилась сказка.

2. Укрощение, воплощение, мечты и реальность


Собственно, как это всё сделать и заставить работать. Итак, посмотрим на наш сферический класс.
  1. TClassItem = class(TCollectionItem)
  2.  private
  3.   FName:String;
  4.   FProperty: TStringList;
  5.   FStorage: TClassItems;
  6.   FPrt:Integer;
  7.  public
  8.   ParentItem: TClassItem;
  9.   constructor Create(Collection: TCollection); override;
  10.   function GetPropAsHTML: String;
  11.   function GetAddress(separator:char): String;
  12.   destructor Destroy; override;
  13.  published
  14.   property Name:String read FName write FName;
  15.   property Parent: Integer read FPrt write FPrt;
  16.   property Prop: TStringList read FProperty write FProperty;
  17.   property Storage: TClassItems read FStorage write FStorage;
  18.  end;
* This source code was highlighted with Source Code Highlighter.


Видим, что есть имя, есть родитель, стринглист со парами «свойство-значение» и хранилище себе подобных. Рассмотрим хранилище себе подобных поближе. Это как раз тот «класс-хранитель», что был выше.
  1. TClassItems = class(TCollection)
  2.  Private
  3.   ParentItem:TClassItem;
  4.   function GetItem(Index: Integer): TClassItem;
  5.   procedure SetItem(Index: Integer; Value: TClassItem);
  6.  public
  7.   function AddOrGet(Name:String): TClassItem;
  8.   function ItemExists(Name:String):Boolean;
  9.   property Items[Index: Integer]: TClassItem read GetItem write SetItem;
  10.  end;
* This source code was highlighted with Source Code Highlighter.


Думаю тут всё более-менее понятно. Далее посмотрим на реализацию класса, который обеспечивал бы хранение в файлах.
  1. TSaveClass = class(TComponent)
  2.  private
  3.   FStorage: TClassItems;
  4.  public
  5.   constructor Create(AOwner: TComponent); override;
  6.   destructor Destroy; override;
  7.  
  8.   Procedure SaveToFile(FileName:String);
  9.   Procedure LoadFromFile(FileName:String);
  10.  
  11.   procedure LoadFromTextFile(FileName: String);
  12.   procedure SaveToTextFile(FileName: String);
  13.  
  14.   Procedure SaveToCompressFile(FileName:String);
  15.   Procedure LoadFromCompressFile(FileName:String);
  16.  
  17.   Function GetItemFromAddress(Adr:String;separator:char):TClassItem;
  18.  
  19.  published
  20.   property Storage: TClassItems read FStorage write FStorage;
  21.  end;
* This source code was highlighted with Source Code Highlighter.


Не сложно заметить, что работает с тремя типами файлов — бинарные, текстовые и (на десерт) со сжатием. Ну и конечно пример работы со всем этим счастьем:
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3. Shkaf,Sections,Section,Polka:TClassItem;
  4. begin
  5. sc:=TSaveClass.Create(nil);
  6. // Создание
  7. Shkaf:=sc.Storage.AddOrGet('Shkaf');
  8. // Задание значений свойств
  9. Shkaf.Prop.Values['W']:='3000';
  10. Shkaf.Prop.Values['H']:='1800';
  11. Shkaf.Prop.Values['D']:='300';
  12. // Создание дочернего объекта
  13. Sections:= Shkaf.Storage.AddOrGet('Sections');
  14.  Section:= Sections.Storage.AddOrGet('Section1');
  15.  Section.Prop.Values['width']:='152';
  16.   Polka:=Section.Storage.AddOrGet('Polka1');
  17.   Polka.Prop.Values['Position']:='450';
  18.   Polka:=Section.Storage.AddOrGet('Polka2');
  19.   Polka.Prop.Values['Position']:='0';
  20.  Section:= Sections.Storage.AddOrGet('Section2');
  21.  Section.Prop.Values['width']:='300';
  22. // Сохраняем в текстовый файл
  23. sc.SaveToTextFile('sc.txt');
  24. // Сохраняем в бинарный файл
  25. sc.SaveToFile('sc.bin');
  26. // Сохраняем в Zip-файл
  27. sc.SaveToCompressFile('sc.z');
  28. //Уничтожаем объект
  29. sc.Free;
  30.    
  31. //Создаём заново
  32. sc:=TSaveClass.Create(Form1);
  33. //Загружаем из Zip-файла
  34. sc.LoadFromCompressFile('sc.z');
  35. //Получаем объект
  36. Shkaf:=sc.Storage.AddOrGet('Shkaf');
  37. //Отображаем его в дереве вместе со всеми потомками
  38. ToTreeViev(Shkaf,Nil);
  39. end;
* This source code was highlighted with Source Code Highlighter.


В данном случае мне надо было сохранить довольно сложную структуру «Шкаф-купе», в котором произвольное количество секций, в каждой секции — полки, ящики и прочие радости быта домохозяек. Тут конечно пример создания руками, в настоящем проекте, это всё делается автоматом и на лету.
image

3. Объективная реальность


Собственно предложенное решение не претендует на шедевр, но согласитесь создание приложений существенно ускорится. Да, я знаю что TstringList не надо было использовать и RTTI не есть “светлый путь познания Дзен”, но всё же я этим пользуюсь по той простой причине, что быстро и главное я оперирую объектами напрямую и использую один и тот же код в разных проектах.

Исходники прилагаются: