Сохранение и загрузка данных в объекты на примере коллекций

Если в Вашей программе используются классы для описания объектов некоторой предметной области, то данные, их инициализирующие, можно хранить и в базе данных. Но можно выбрать гораздо более продуктивный подход, который доступен в Delphi/C++ Builder. Среда разработки Delphi/C++ Builder хранит ресурсы всех форм в двоичных или текстовых файлах и эта возможность доступна и для разрабатываемых с ее помощью программ. В данном случае, для оценки удобств такого подхода лучше всего рассмотреть конкретный пример.

Необходимо реализовать хранение информации о некоей службе рассылки и ее подписчиках. Будем хранить данные о почтовом сервере и список подписчиков. Каждая запись о подписчике хранит его личные данные и адрес, а также список тем(или каталогов), на которые он подписан. Как большие поклонники Гради Буча (Grady Booch), а также будучи заинтересованы в удобной организации кода, мы организуем информацию о подписчиках в виде объектов. В Delphi для данной задачи идеально подходит класс TCollection, реализующий всю необходимую функциональность для работы со списками типизированных объектов. Для этого мы наследуемся от TCollection, называя новый класс TMailList - список рассылки, а также создаем наследника от TCollectionItem - TMailClient - адресат рассылки. Последний будет содержать все необходимые данные о подписчике, а также реализовывать необходимые функции для работы с ним.

Саму коллекцию с подписчиками нам нужно будет поместить в некий базовый класс, который мы и будем сохранять и загружать. На роль такового подходит класс TMailer - почтовый клиент.
Начнем с TMailClient.


type   
  TMailClient = class(TCollectionItem)   
  private   
    FName: string;   
    FAddress: string;   
    FEnabled: boolean;   
    FFolders: TStringList;   
  public   
    Files: TStringList;  // список файлов к рассылке. заполняется в run-time. Сохранению не подлежит    
    constructor Create(Collection: TCollection); override;   
    destructor Destroy; override;   
    procedure PickFiles;   
  published   
    property Name: string read FName write FName;  // имя адресата   
    property Address: string read FAddress write FAddress; // почтовый адрес   
    property Enabled: boolean read FEnabled write FEnabled default true;  
    property Folders: TStringList read FFolders write FFolders; // список папок (тем) подписки   
  end;  

Класс содержит сведения о имени клиента, его адресе, его статусе(Enabled), а также список каталогов, на которые он подписан. Процедура PickFiles составляет список файлов к отправке и сохраняет его в свойстве Files
Класс TMailList, хранящий объекты класса TMailClient, приведен ниже.


TMailList = class(TCollection)   
public   
  function GetMailClient(Index: Integer): TMailClient;   
  procedure SetMailClient(Index: Integer; Value: TMailClient);   
public   
  function  Add: TMailClient;   
  property Items[Index: Integer]: TMailClient read GetMailClient  write SetMailClient; default;   
end;  

Теперь поместим класс TMailList в класс TMailer. В него можно будет потом включить данные о параметрах доступа к почтовому серверу для отправки почты. Он мог бы и отправлять почту, но в данном примере это не использовано, дабы не перегружать код.

То есть в нашем примере он выполняет только роль носителя данных о подписчиках и их подписке. Класс TComponent, от которого он наследуется можно сохранить в файл, в то время как TCollection самостоятельно не сохранится. Только если она агрегирована в TComponent. Именно это у нас и реализовано.


TMailer = class(TComponent)   
private   
  FMailList: TMailList;   
public   
  constructor Create(AOwner: TComponent); override;   
  destructor Destroy; override;   
published   
  property MailList: TMailList read FMailList write FMailList; // коллекция - список рассылки.   
  // здесь можно поместить, к примеру, данные о соединении с почтовым сервером    
end;  

Повторюсь. В данном случае мы наследуемся от класса TComponent, для того, чтобы была возможности записи данных объекта в файл. Свойство MailList содержит уже объект класса TMailList.
Реализация всех приведенных классов приведена ниже.


constructor TMailClient.Create(Collection: TCollection);   
begin   
  inherited;   
  Folders := TStringList.Create;   
  Files := TStringList.Create;   
  FEnabled := true;   
end;   
   
destructor TMailClient.Destroy;   
begin   
  Folders.Free;   
  Files.Free;   
  inherited;   
end;   
  
// здесь во всех каталогах Folders ищем файлы для рассылки и помещаем их в Files.   
procedure TMailClient.PickFiles;   
var i: integer;  
begin   
    for i:=0 to Folders.Count-1 do CreateFileList(Files, Folders[i]);   
end;   
  
// Стандартный код при наследовании от класса коллекции: переопределяем тип    
function TMailList.GetMailClient(Index: Integer): TMailClient;   
begin   
  Result := TMailClient(inherited Items[Index]);   
end;   
   
// Стандартный код при наследовании от класса коллекции    
procedure TMailList.SetMailClient(Index: Integer; Value: TMailClient);   
begin   
  Items[Index].Assign(Value);   
end;   
  
 // Стандартный код при наследовании от класса коллекции: переопределяем тип    
function TMailList.Add: TMailClient;   
begin   
  Result := TMailClient(inherited Add);   
end;   
  
// создаем коллекцию адресатов рассылки TMailList   
constructor TMailer.Create(AOwner: TComponent);   
begin   
  inherited Create(AOwner);   
  MailList := TMailList.Create(TMailClient);   
end;   
   
destructor TMailer.Destroy;   
begin   
  MailList.Free;   
  inherited;   
end;   
//---------------------  

Функция CreateFileList создает по каким-либо правилам список файлов на основе переданного ей списка каталогов, обходя их рекурсивно. К примеру, она может быть реализована так.


procedure CreateFileList(sl: TStringList; const FilePath: string);   
var   
  sr: TSearchRec;   
  procedure ProcessFile;   
  begin   
    if (sr.Name = '.')or(sr.Name = '..') then exit;   
    if sr.Attr <> faDirectory then   
      sl.Add(FilePath + '\' + sr.Name);   
    if sr.Attr = faDirectory then   
    begin   
      CreateFileList(sl, FilePath + '\' + sr.Name);   
    end;   
  end;   
begin   
  if not DirectoryExists(FilePath) then exit;   
  if FindFirst(FilePath + '\' + '*.*', faAnyFile , sr) = 0 then ProcessFile;   
  while FindNext(sr) = 0 do ProcessFile;   
  FindClose(sr);   
end;  

В итоге мы располагаем классом TMailer, содержащим всю необходимую нам информацию. Теперь перейдем к созданию объекта, их сохранению и загрузке.


var   
  Mailer: TMailer; // это наш объект для хранения данных о почтовой рассылки  
    
// Процедура загрузки данных в объект. Может быть процедурой OnCreate() главной формы.  
procedure TfMain.FormCreate(Sender: TObject);  
var   
  sDataFile, sTmp: string;   
  i, j: integer;  
begin   
   
  Mailer := TMailer.Create(self);   
   
 // будем считать, что данные были сохранены в файл users.dat в каталоге программы  
  sDataFile := ExtractFilePath(ParamStr(0)) + 'users.dat';   
   
  //...загрузка данных из файла   
  if FileExists(sDataFile) then  
    LoadComponentFromTextFile(Mailer, sDataFile);   
   { здесь данные из файла загружены }  
  
  //...перебор подписчиков   
  for i:=0 to Mailer.MailList.Count-1 do   
  begin   
  
    sTmp := Mailer.MailList[i].Name;  //...обращение к имени   
    sTmp := Mailer.MailList[i].Address; //...обращение к адресу   
    //... sTmp - фиктивная переменная. Поменяйте ее на свои.    
      
    Mailer.MailList[i].PickFiles;  //... поиск файлов для отправки очередному подписчику.   
   
   //...перебор найденных файлов к отправке   
    for j:=0 to Mailer.MailList[i].Files.Count-1 do   
    begin   
      sTmp := Mailer.MailList[i].Files[j];   
    end;  
      
  end;  
end;  

После загрузки данных мы можем работать с данными в нашей коллекции подписчиков. Добавлять и удалять их ( Mailer.MailList.Add; Mailer.MailList.Delete(Index); ). При завершении работы программы необходимо сохранить уже новые данные в тот же файл.


// Процедура сохранения данных из объекта в файл. Может быть процедурой OnDestroy() главной формы.  
procedure TfMain.OnDestroy;  
begin  
  //...сохранение данных в файл users.dat  
  SaveComponentToTextFile(Mailer, ExtractFilePath(ParamStr(0)) + 'users.dat');   
end;  

Хранение данных в файле позволяет оказаться от использования БД, если объем данных не слишком велик и нет необходимости в совместном доступе к данным.
Самое главное - мы организуем все данные в виде набора удобных для работы классов и не тратим время на их сохранение и инициализацию из БД.
Приведенный пример лишь иллюстрирует этот подход. Для его реализации могут подойти и 2 таблицы в БД. Однако приведенный подход удобен при условии, что данные имеют сложную иерархию. К примеру, вложенные коллекции разных типов гораздо сложнее разложить в базе данных, для их извлечения потребуется SQL. Решайте сами, судя по своей конкретной задаче.

Далее приведен код функций для сохранения/чтения компонента.


//...процедура загружает(инициализирует) компонент из текстового файла с ресурсом   
procedure LoadComponentFromTextFile(Component: TComponent; const FileName: string);   
var   
  ms: TMemoryStream;   
  fs: TFileStream;   
begin   
  fs := TFileStream.Create(FileName, fmOpenRead);   
  ms := TMemoryStream.Create;   
  try   
    ObjectTextToBinary(fs, ms);   
    ms.position := 0;   
    ms.ReadComponent(Component);   
  finally   
    ms.Free;   
    fs.free;   
  end;   
end;   
   
//...процедура сохраняет компонент в текстовый файл   
procedure SaveComponentToTextFile(Component: TComponent; const FileName: string);   
var   
  ms: TMemoryStream;   
  fs: TFileStream;   
begin   
  fs := TFileStream.Create(FileName, fmCreate or fmOpenWrite);   
  ms := TMemoryStream.Create;   
  try   
    ms.WriteComponent(Component);   
    ms.position := 0;   
    ObjectBinaryToText(ms, fs);   
  finally   
    ms.Free;   
    fs.free;   
  end;   
end;  
Теги:
загрузка данных
Добавлено: 07 Августа 2018 08:25:36 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...