Если в Вашей программе используются
классы для описания объектов некоторой предметной области, то данные, их инициализирующие,
можно хранить и в базе данных. Но можно выбрать гораздо более продуктивный подход,
который доступен в Delphi/C++ Builder. Среда разработки Delphi/C++ Builder хранит
ресурсы всех форм в двоичных или текстовых файлах и эта возможность доступна
и для разрабатываемых с ее помощью программ. В данном случае, для оценки удобств
такого подхода лучше всего рассмотреть конкретный пример.
Необходимо реализовать хранение
информации о некоей службе рассылки и ее подписчиках. Будем хранить данные о
почтовом сервере и список подписчиков. Каждая запись о подписчике хранит его
личные данные и адрес, а также список тем(или каталогов), на которые он подписан.
Как большие поклонники Гради Буча (Grady Booch), а также будучи заинтересованы
в удобной организации кода, мы организуем информацию о подписчиках в виде объектов.
В Delphi для данной задачи идеально подходит класс TCollection, реализующий
всю необходимую функциональность для работы со списками типизированных объектов.
Для этого мы наследуемся от TCollection, называя новый класс TMailList - список
рассылки, а также создаем наследника от TCollectionItem - TMailClient - адресат
рассылки. Последний будет содержать все необходимые данные о подписчике, а также
реализовывать необходимые функции для работы с ним.
Саму коллекцию с подписчиками нам нужно будет поместить
в некий базовый класс, который мы и будем сохранять и загружать. На роль такового
подходит класс TMailer - почтовый клиент.
Теперь поместим класс TMailList
в класс TMailer. В него можно будет потом включить данные о параметрах доступа
к почтовому серверу для отправки почты. Он мог бы и отправлять почту, но в данном
примере это не использовано, дабы не перегружать код.
То есть в нашем примере
он выполняет только роль носителя данных о подписчиках и их подписке. Класс
TComponent, от которого он наследуется можно сохранить в файл, в то время как
TCollection самостоятельно не сохранится. Только если она агрегирована в TComponent.
Именно это у нас и реализовано.
Функция CreateFileList создает
по каким-либо правилам список файлов на основе переданного ей списка каталогов,
обходя их рекурсивно. К примеру, она может быть реализована так.
В итоге мы располагаем классом
TMailer, содержащим всю необходимую нам информацию. Теперь перейдем к созданию
объекта, их сохранению и загрузке.
После загрузки данных мы
можем работать с данными в нашей коллекции подписчиков. Добавлять и удалять
их ( Mailer.MailList.Add; Mailer.MailList.Delete(Index); ). При завершении работы
программы необходимо сохранить уже новые данные в тот же файл.
Начнем с 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
;
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
;
//---------------------
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
;
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;
// Процедура сохранения данных из объекта в файл. Может быть процедурой OnDestroy() главной формы.
procedure
TfMain.OnDestroy;
begin
//...сохранение данных в файл users.dat
Хранение данных в файле
позволяет оказаться от использования БД, если объем данных не слишком велик
и нет необходимости в совместном доступе к данным.
Далее приведен код функций
для сохранения/чтения компонента.
Самое главное - мы организуем все данные в виде набора удобных для работы
классов и не тратим время на их сохранение и инициализацию из БД.
Приведенный пример лишь иллюстрирует этот подход. Для его реализации могут подойти
и 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
;