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



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

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

Начнем с TMailClient.


type
TMailClient = class(TCollectionItem)
private
FName: string;
FAddress: string;
FEnabled: boolean;
FFolders: TStringList;
public
Files: TStringList;
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, содержащий также и данные о параметрах доступа к почтовому серверу для отправки почты.


TMailer = class(TComponent)
private
name: string;
EMail: string;
FMailList: TMailList;
FLastActivated: TDateTime;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property MailList: TMailList read FMailList write FMailList;
property LastActivated: TDateTime read FLastActivated write FLastActivated;
end;

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

Реализация всех приведенных классов приведена ниже.


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;
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;
constructor TMailer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
MailList := TMailList.Create(TMailClient);
FLastActivated := now;
end;
destructor TMailer.Destroy;
begin
MailList.Free;
inherited;
end;

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


procedure CreateFileList(sl: TStringList; 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
CreateFileList(sl, FilePath + '\' + sr.name);
end;
begin
if not DirectoryExists(FilePath) then
exit;
if FindFirst(FilePath + '\' + Settings.IncludeFileMasks,
faAnyFile , sr) = 0 then
ProcessFile;
while FindNext(sr) = 0 do
ProcessFile;
FindClose(sr);
end;

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


var
sDataFile: string;
MLN: TMailLateNight;
begin
MLN := TMailLateNight.Create(nil);
sDataFile := ExtractFilePath(ParamStr(0)) + 'users.dat';
//...загрузка данных из файла
if FileExists(sDatsFile) then
LoadUsersFromTextFile(MLN, sDatsFile);
...
//...работа с объектами
for i:=0 to MLN.MailList.Count-1 do
begin
s := MLN.MailList[i].name;
s := MLN.MailList[i].Address;
MLN.MailList[i].PickFiles;
for j:=0 to MLN.Files.Count-1 do
begin
s := MLN.MailList[i].Files[j];
...
//...сохранение данных в файл
SaveComponentToTextFile(MLN, sDataFile);

Хранение данных в файле позволяет оказаться от использования БД, если объем данных не слишком велик и нет необходимости в совместном доступе к данным.

Самое главное - мы организуем все данные в виде набора удобных для работы классов и не тратим время на их сохранение и инициализацию из БД.

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


//...процедура удаляет все дочерние компоненты из формы
procedure DeleteComponents(Form: TForm);
var
i: integer;
begin
for i := Form.ComponentCount - 1 downto 0 do
Form.Components[i].Free;
end;
// ...процедура загружает(инициализирует)
// компонент из текстового файла с ресурсом
procedure LoadComponentFromTextFile(Component: TComponent;
FileName: string);
var
ms: TMemoryStream;
fs: TFileStream;
begin
ms := TMemoryStream.Create;
fs := TFileStream.Create(FileName, fmOpenRead);
try
ObjectTextToBinary(fs, ms);
ms.position := 0;
ms.ReadComponent(Component);
finally
ms.Free;
fs.free;
end;
end;
//...процедура сохраняет компонент в текстовый файл
procedure SaveComponentToTextFile(Component: TComponent;
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;


Далее: Класс для реализации списка Variant-ов на основе TCollection »»