--- //[[romeeras@gmail.com|Romtek]] 2009/10/30 21:38//
====== Построение базы данных с применением коллекций ======
===== Введение =====
Коллекции - это одно из многочисленных применений ООП в программах.
Предлагаю вашему вниманию базу данных на основе коллекций. В ней мы рассмотрим такие операции
над коллекцией, как //добавление, редактирование, просмотр, поиск в коллекции, сохранения
и загрузки коллекции//.
Почему я решил показать именно на примере коллекций? Да потому, что я вижу их как очень удобную
структуру для хранения самой разной информации.
===== Использование коллекций =====
Что можно сделать с коллекциями:
* Добавлять объекты в коллекцию
* Изменять объекты в коллекции
* Удалять объекты из коллекции
* Искать объекты в коллекции по заданному критерию
* Сохранять коллекции в файл
* Читать коллекции с файла
Всё это позволяет механизм наследования. Таким образом, наследовав потомка с новыми характеристиками (допустим, имеющий собственный способ упорядочивания объектов в коллекции), можно добиться новой функциональности программы.
==== Объект ====
Возьмём для примера такую структуру:
type
PRecord = ^TRecord;
TRecord = record
Name: String[12];
Mark: Integer;
end;
Для того, чтобы работать с базой данных все знают, что нам нужен массив, хранящий эти записи:
type
TDataArr = array [1..N] of TRecord;
DB: file of TDataArr; { описание файловой переменной для работы с файлом }
Для коллекции же необходимо создать объект, в котором будет находиться переменная (в данном случае //TransferRecord//)
типа //TRecord //(запись):
PStudentInfo=^TStudentInfo;
TStudentInfo=object(TObject)
TransferRecord: TRecord;
constructor Load(var S: TStream); { необходимо перекрывать для операции сохранения в поток (см. ниже) }
procedure Store(var S: TStream); { необходимо перекрывать для операции чтения с потока (см. ниже) }
Procedure Print;
end;
==== Сохранение объекта в поток и чтение с него (I/O) ====
Метод //Store// нужен для сохранения объекта в поток, а конструктор //Load// - для его загрузки из потока.
Метод //Print// выводит на экран данные о студенте из //TransferRecord//. Это, собственно, всё, что нам
требуется от объекта //TStudentInfo//.\\
Остальные операции будут проводиться с коллекцией.
==== Сортировка ====
PStudentsColl=^TStudentsColl;
TStudentsColl=object(TSortedCollection)
function Compare(Key1, Key2: Pointer): Integer; virtual; { перекрываем метод сравнения }
end;
//TSortedCollection// является потомком обычной коллекции //TCollection//, только у неё имеется возможность
сортировки объектов. Т.к. функция сравнения //Compare// (с её помощью сортируется коллекция) является
абстрактной, её надо перекрывать, что мы и сделали:
function TStudentsColl.Compare(Key1, Key2: Pointer): Integer;
begin
Compare := StrICmp (
PStudentInfo(Key1)^.TransferRecord.Name,
PStudentInfo(Key2)^.TransferRecord.Name
);
end;
Здесь сравнение ведётся по имени студентов с помощью функции сравнения строк //StrICmp //(файл sortfunc.inc),
без учёта регистра букв. Можно сортировать по любому типу поля любыми видами сравнений.
==== Регистрация ====
В разделе инициализации модуля следует зарегистрировать типы //RStudentsColl// и //RStudentInfo//, чтобы не
возникало ошибок при работе с потоками:
RegisterType (RStudentsColl);
RegisterType (RStudentInfo);
== Используемые в коллекции функции и процедуры ==
//At// - указатель на объект в коллекции\\ //Insert// - добавление в коллекцию\\ //AtDelete// - удаление объекта из коллекции\\ //FirstThat //- итератор для поиска по заданному условию.
===== Исходный код =====
Сам код программы я старался прокоментировать как можно более подробно:
Unit StudObj;
Interface
Uses Objects;
type
PRecord = ^TRecord;
TRecord = record
Name: String[12];
Mark: Integer;
end;
PStudentInfo=^TStudentInfo;
TStudentInfo=object(TObject)
TransferRecord: TRecord;
constructor Load(var S: TStream);
procedure Store(var S: TStream);
Procedure Print; Virtual;
end;
PStudentsColl=^TStudentsColl;
TStudentsColl=object(TSortedCollection)
function Compare(Key1, Key2: Pointer): Integer; virtual;
procedure Error(Code, Info: Integer); virtual;
end;
const
RStudentInfo: TStreamRec = (
ObjType: 55001;
VmtLink: Ofs(TypeOf(TStudentInfo)^);
Load: @TStudentInfo.Load;
Store: @TStudentInfo.Store
);
RStudentsColl: TStreamRec = (
ObjType: 55002;
VmtLink: Ofs(TypeOf(TStudentsColl)^);
Load: @TStudentsColl.Load;
Store: @TStudentsColl.Store);
var
PC: PStudentsColl;
Implementation
Uses Drivers;
type
TLinkRecord = record
PName: PString;
PMark: longint;
end;
constructor TStudentInfo.Load(var S: TStream);
begin
Inherited Init;
S.Read(TransferRecord, SizeOf(TransferRecord));
end;
procedure TStudentInfo.Store(var S: TStream);
begin
S.Write(TransferRecord, SizeOf(TransferRecord));
end;
Procedure TStudentInfo.Print;
var str: string;
LinkRecord: TLinkRecord;
Begin
with LinkRecord do
begin
PName := @TransferRecord.Name;
PMark := TransferRecord.Mark;
end;
FormatStr (str,' %-24s%-4d', LinkRecord);
writeln (str);
End;
procedure TStudentsColl.Error (Code, Info: Integer);
begin
write ('Error: ');
case Code of
coIndexError: writeln ('Index out of range.');
coOverflow : writeln ('Collection overflow.');
end;
end;
{$i sortfunc.inc}
function TStudentsColl.Compare (Key1, Key2: Pointer): Integer;
begin
Compare := StrICmp (
PStudentInfo(Key1)^.TransferRecord.Name,
PStudentInfo(Key2)^.TransferRecord.Name
);
end;
begin
RegisterType (RStudentsColl);
RegisterType (RStudentInfo);
end.
{
Keywords: OOP, Collection, Database
Программа является базой данных на основе коллекций.
Используются операции добавления, редактирования, удаления, просмотра, поиска в коллекции,
сохранения и загрузки коллекции.
Примечание: программу несложно изменить для использования интерфейса Turbo Vision.
Romtek (c) May 2005
}
uses crt, objects, drivers, strings, StudObj;
const
db_file = 'stud_db.dat';
maxitems = 9;
OptionsList: array [1..maxitems] of string[30] = (
'Load collection from file',
'Save collection to file',
'View collection',
'Add record',
'Edit record',
'Delete record',
'Search record in collection',
'Best students',
'Quit from program'
);
var
Created,
Quit: boolean;
option: integer;
procedure AbortMsg (msg: string);
{ Вывести сообщение об ошибке и выйти из программы }
begin
writeln (msg);
halt;
end;
procedure LoadCollection;
{ Загрузка сохраненной ранее коллекции из бинарного файла коллекции }
var
FS: TBufStream;
begin
FS.Init (db_file, stOpen,1024);
if FS.Status <> stOK then
AbortMsg ('File not found.'); { Файл не найден }
PC := PStudentsColl (FS.Get); { Вставить коллекцию из файла }
FS.Done;
if FS.Status <> stOK then
AbortMsg ('Error of loading collection.');
{ Произошла ошибка чтения коллекции. Подробности искать в справке по 'stXXX' }
writeln;
writeln ('Collection is loaded successfully.'); { Сообщение об успешно загруженной коллекции }
readln;
end;
procedure SaveCollection;
{ Сохранить коллекцию в файл }
var
FS: TBufStream;
begin
FS.Init (db_file, stCreate,1024); { Открываем файл коллекции на создание }
FS.Put (PC); { Вставляем коллекцию в файл }
FS.Done; { Закрываем работу с потоком }
if FS.Status <> stOK then
AbortMsg ('Error while saving collection!'); { Произошла ошибка при сохранении коллекции }
writeln;
writeln ('Collection is stored successfully.'); { Сообщение об успешно сохраненной коллекции }
readln;
end;
procedure ViewCollection (C: PCollection);
procedure CallPrint (P : PStudentInfo); far;
{ Обязательно должна присутствовать директива FAR !
Кроме того, процедура должна быть вложенной, иначе возникнут проблемы. }
begin
P^.Print;
end;
begin { Print }
Writeln;
Writeln;
Writeln ('Student list:');
{ помощью итератора ForEach выводим информацию о каждом студенте }
C^.ForEach (@CallPrint);
readln;
end;
procedure PrintMatches (C: PCollection; _Mark: integer);
{ Подсчёт и вывод студентов, имеющих оценку свыше указанной _Mark }
var count: word;
procedure Match (P : PStudentInfo); far;
begin
if PStudentInfo(P)^.TransferRecord.Mark >= _Mark then
begin
P^.Print;
inc (count);
end;
end;
begin { Print }
count:=0;
Writeln (#13#10'Students who have mark above ', _Mark,': ');
C^.ForEach (@Match);
writeln;
Writeln ('Total: ', count);
readln;
end;
procedure BestStudents;
begin
PrintMatches (PC, 80); { Искать студентов, имеющих балл свыше 80 }
end;
procedure SearchRecord;
var
P: PStudentInfo;
i: integer;
who: string;
function Matches (Item: Pointer): Boolean; far;
begin
Matches := PStudentInfo (Item)^.TransferRecord.Name = Who;
end;
begin
write ('Look for student (name): '); readln (who);
{ Используйте итератор FirstThat, чтобы найти первую "карточку",
удовлетворяющую условию }
P := PC^.FirstThat (@Matches);
i := PC^.IndexOf (P);
if i >= 0 then
begin
writeln ('Student was found:');
PStudentInfo(PC^.At(i))^.Print;
end
else writeln ('"', Who ,'" was not found.');
readln;
end;
procedure AddRecord;
{ Добавление "карточки" в коллекцию }
var
SI: PStudentInfo;
Rec: TRecord;
begin
writeln('Enter some info about a student: ');
SI := New (PStudentInfo, Init); { Создаем "карточку" студента }
with SI^.TransferRecord do
begin
write ('Name: ' : 8); readln (Name); { Вводим новые фамилию }
write ('Mark: ' : 8); readln (Mark); { и оценку }
end;
PC^.Insert (SI); { Вставляем данные о студенте в коллекцию }
writeln;
writeln ('Record stored successfully.'); { Сообщение об успешно добавленной "карточке" }
readln;
end;
procedure EditRecord;
{ Редактировать заданную "карточку" студента }
var
SI: PStudentInfo;
Rec: TRecord;
num: integer;
begin
{ Прежде чем заменять данные в "карточке" студента, надо ввести его номер в коллекции.
Поэтому для начала нужно просмотреть список выбором 'View collection' }
write ('Enter number of student in list: '); readln (num);
SI := PC^.At (Pred (num)); { Присваиваем указатель на "карточку" студента под номером num-1.
Не забывайте, что нумерация в коллекциях идет с нуля! }
with SI^.TransferRecord do
begin
SI^.Print; { Показываем текущие данные из "карточки" студента }
writeln;
write ('Enter new name: '); readln (Name); { Вводим новые фамилию }
write ('Enter new mark: '); readln (Mark); { и оценку }
end;
writeln;
writeln ('Record stored successfully.'); { Сообщение об успешно измененной "карточке" }
readln;
end;
procedure DeleteRecord;
{ Удаление "карточки" из коллекции }
var
SI: PStudentInfo;
Rec: TRecord;
num: integer;
begin
{ Прежде чем удалять данные в "карточке" студента, надо ввести его номер в коллекции.
Поэтому для начала нужно просмотреть список выбором 'View collection' }
write ('Enter number of student in list: '); readln (num);
PC^.AtDelete (Pred (num));
writeln;
writeln ('Record deleted successfully.'); { Сообщение об успешно измененной "карточки" }
readln;
end;
procedure ShowMenu;
{ Построение меню и выбор операции }
var
i: integer;
begin
clrscr;
writeln ('-= Menu =-': 46);
for i := 1 to maxitems do
begin
writeln;
writeln (i: 26, '. ', OptionsList[i]); { Вывод элементов меню }
end;
writeln;
writeln;
write ('Enter option: ': 43); readln (option);
end;
BEGIN
Created := true;
PC:=New(PStudentsColl,Init(50,10));
{ Создание коллекции в памяти из 50 объектов.
Цифра 10 говорит о том, что если попытаемся вставить в коллекцию больше чем 50 объектов,
то станем увеличивать начальный размер (50) на 10, пока не хватит места для всех объектов. }
if PC = Nil then { Увы, не хватило памяти... Выходим. }
exit;
repeat
ShowMenu;
{ Выбор опции }
case option of
1: if PC^.Count = 0 then { Коллекция пуста }
LoadCollection;
2: SaveCollection;
3: ViewCollection (PC);
4: AddRecord;
5: EditRecord;
6: DeleteRecord;
7: SearchRecord;
8: BestStudents;
9: Quit := true; { Получили команду выйти из программы }
end;
until Quit;
{ Освободить память от коллекции }
Dispose(PC, Done);
END.
**Полная** версия находится в файле.
{{pascal:oop:stud_db.zip|Скачать stud_db.zip}}
===== База данных на примере коллекции с интерфейсом Turbo Vision =====
Программа основана на программе выше, только добавлен интерсфейс Turbo Vision (как среда BP7). Аналогично, рассмотрены и реализованы следующие операции: добавление, редактирование, удаление, просмотр, поиск в коллекции, сохранения и загрузки коллекции в файл.
{{pascal:oop:tv_stud.zip|Скачать tv_stud.zip}}