Применяю это и в персерах. Вернее, при написании приложений, где необходим парсинг и последующая обработка полученных данных, но где использование базы данных было бы излишеством.
Самый простой пример такой объектной организации:
TBASetting = class
BA : string;
MM_CONST : integer;
STRIKESTEP,
MIN_STRIKE,
MAX_STRIKE : double;
public
constructor Create(const aPath: string; const aParams: TStrings); overload;
...
end;
TBASettings = class(TList)
...
function LoadFromINI (IniFile : TIniFile) : boolean;
function SaveToINI (IniFile: TIniFile) : boolean;
procedure Clear; override;
end;
Остановлюсь подробнее на следующих пунктах:
- создание объектов, заполнение списка;
- освобождение памяти при "очистке" списка.
Создание объектов и добавление их в список
Опять же, два варианта.
1. Создание одиночного объекта (выделение памяти, присвоение значений полям), добавление в список.
tmpSet := TBASetting.Create;
tmpSet.BA := sBA;
tmpSet.MM_CONST := iMM_CONST;
tmpSet.STRIKESTEP := dSTRIKESTEP;
tmpSet.MIN_STRIKE := dMIN_STRIKE;
tmpSet.MAX_STRIKE := dMAX_STRIKE;
BASettings.Add(tmpSet);
2. Массовое заполнение списка объектами (групповое добавление объектов). Этот вариант удобен, если, например, считывать данные из ini-файла или XML.
function TBASettings.LoadFromINI(IniFile: TIniFile): boolean;
var
Sections,
SecParams : TStringList;
i : integer;
sVal : string;
begin
Result := false;
Sections := TStringList.Create;
try
IniFile.ReadSections(Sections);
SecParams := TStringList.Create;
try
for i:=0 to Sections.Count-1 do
begin
sVal := Sections[i];
// ну, тут всякие условия, проверки, код для "отсева" и т.д.
if sVal <> 'COMMON' then
begin
IniFile.ReadSectionValues(sVal, SecParams);
if SecParams.Count > 0 then
begin
BASettings.Add(TBASetting.Create(sVal, SecParams));
end
end;
end;
finally
SecParams.Free;
end;
finally
Sections.Free;
end;
Result := true;
end;
constructor TBASetting.Create(const aPath: string;
const aParams: TStrings);
begin
BA := aPath;
MM_CONST := StrToIntDef(aParams.Values['MM_CONST'], 4);
STRIKESTEP := StrToFloatDef(aParams.Values['STRIKESTEP'], 0);
MIN_STRIKE := StrToFloatDef(aParams.Values['MIN_STRIKE'], 0);
MAX_STRIKE := StrToFloatDef(aParams.Values['MAX_STRIKE'], 0);
end;
Очистка списка объектов (наследника TList)
При удалении единичных экземпляров (объектов) из списка надо освобождать память каждого объекта, а уже потом удалять его из списка.
Что делать при удалении всего списка? У TList есть метод Free, который освобождает память и удаляет объект. Казалось бы, надо у нашего списка перегрузить этот метод? Но правильнее перегрузить метод Clear (который, в свою очередь, вызывается из Free, а Free вызывается FreeAndNil-ом).
TBASettings = class(TList)
...
function LoadFromINI (IniFile : TIniFile) : boolean;
function SaveToINI (IniFile: TIniFile) : boolean;
procedure Clear; override;
end;
procedure TBASettings.Clear;
var
i : integer;
tmpSet : TBASetting;
begin
for i := 0 to Count-1 do
begin
tmpSet := TBASetting(Items[i]);
tmpSet.Free;
end;
inherited;
end;
Если в составе класса в качестве полей есть экземпляры других классов, то надо:
- перегрузить конструктор, выделить в нем память под эти поля;
- перегрузить деструктор объекта, в котором освободить память, выделенную при создании.
Листинг для примера:
TBASetting = class
BA : string;
PUTNAME,
CALLNAME : TStringList;
MM_CONST : integer;
STRIKESTEP,
MIN_STRIKE,
MAX_STRIKE : double;
public
...
constructor Create(const aPath: string; const aParams: TStrings); overload;
constructor Create; overload;
destructor Destroy; override;
end;
constructor TBASetting.Create;
begin
PUTNAME := TStringList.Create;
CALLNAME := TStringList.Create;
end;
destructor TBASetting.Destroy;
begin
PUTNAME.Free;
CALLNAME.Free;
end;
Я тут привожу условный листинг, не особо заботясь о "классических правилах" работы с классами. Вообще, в небольших проектиках, которые пишу под свои задачи, не парюсь над защищенностью наследуемых и ненаследуемых методов, всяких протектедах и доступах к полям. Считаю, что это лишнее, хотя, может, кто-то с этим и не согласится.
На сегодня это все, о чем я хотела поведать. Пойду дальше программировать :)
P.S.: Производительность труда заметно повышается, когда дома ломается компьютер. На работе приходится брать себя в руки и работать! :D
___
Чтобы быть в курсе обновлений блога, можно подписаться на RSS.
В таком случае лучше использовать TObjectList:
ОтветитьУдалить1) элементы списка - объекты (TObject), а не указатели (Pointer).
2) при очистке он освобождает каждый элемент списка автоматически.
А еще лучше - использовать современные версии Delphi: 2009, 2010, ХЕ. Там есть дженерики.
PS. Производительность труда возрастает еще больше, если использовать современные инструменты :)
Полностью согласен с Frantic по поводу TObjectList вместо TList.
ОтветитьУдалитьНо хотел бы ещё напомнить о старой конструкции типа:
var
Mas: array of
record
AA: Integer;
BB: Integer;
end;
Плюсы коротко и быстро(быстрее чем TList).
Минусы не ООП (Но в вашем примере объекты одного класса и не имеют методов кроме Создания, только поля).
Юрий
Так использовать наследование плохо - нарушаем принцип Лисков и любой проект размером больше маленького за такое жестоко накажет.
ОтветитьУдалитьЕсли нужен специальный список, то кошерно использовать композицию - вставить TList внутрь в виде приватного поля и делегировать к нему все нужные для специфического списка вызовы.
Frantic, да, я вообще торможу с освоением новых инструментов) У меня до сих пор Delphi 7 (просто потому, что на работе приходится иметь дело с семеркой).
ОтветитьУдалитьПо поводу TObjectList - да, согласна, но привычка - великая вещь.)
Юрий, если я использую список объектов, то мне нужно именно ООП. Я просто в листинге потерла все ненужные процедуры/функции.
Кирилл, пошла гуглить про принцип Лисков :)
Спасибо за комментарии! Буду умнеть под чутким руководством общественности :) Авось вырасту когда-нибудь из баз данных... :D
Странно, почему для удаления списка надо перекрывать метод Clear. А если надо удалить I-й элемент?
ОтветитьУдалитьВ TList специально для таких случаев есть метод Notify. В Вашем случае будет что-то типа такого:
procedure TBASettings.Notify(Ptr: Pointer; Action: TListNotification);
begin
if Action = lnDeleted then TBASetting(Ptr).Free;
inherited Notify(Ptr, Action);
end;
Еще нюанс: можно избежать использования вложенных try..finally
ОтветитьУдалитьfunction TBASettings.LoadFromINI(IniFile: TIniFile): boolean;
var
Sections,
SecParams : TStringList;
i : integer;
sVal : string;
begin
Result := false;
SecParams := nil;
Sections := TStringList.Create;
try
IniFile.ReadSections(Sections);
SecParams := TStringList.Create;
for i:=0 to Sections.Count-1 do
begin
sVal := Sections[i];
// ну, тут всякие условия, проверки, код для "отсева" и т.д.
if sVal <> 'COMMON' then
begin
IniFile.ReadSectionValues(sVal, SecParams);
if SecParams.Count > 0 then
begin
BASettings.Add(TBASetting.Create(sVal, SecParams));
end
end;
end;
finally
FreeAndNil(SecParams);
FreeAndNil(Sections);
end;
Result := true;
end;
По поводу цикла для очистки элементов списка. Обычно делают что то типа
ОтветитьУдалитьfor i:=List.Count-1 downto 0 do
List[i].Free
так меньше вызовов Count и нет головной боли относительно - список заметит удаление элемента и сожмется или нет.
> так меньше вызовов Count и нет головной боли относительно - список заметит удаление элемента и сожмется или нет.
ОтветитьУдалитьЧто за бред?
1. Count в любом цикле for (и по возрастанию, и по убыванию) вызовется один раз
2. "Сжатие" списка во время его очистки невозможно, т.к. приложение однопоточное. Если потоков несколько - то работа со списком идет в критической секции => в каждый момент времени поток один. Если критической секции (или другого метода синхронизации) нет - то программист идиот и такая программа будет глючить не только при очистке, а вообще повсюду.
Не совсем понятно, почему отношение "реализуется посредством" фактически вылилось в вашем случае в наследование TList, у которого виртуальных методов-то всего два - Error и Notify... Логично применять такое наследование в тех случаях, когда вам остро необходим доступ к protected-мемберам, но здесь такой необходимости не видно. Поэтому, в общем случае, как мне кажется, лучше было бы применить в подобной ситуации агрегацию. Вы ведь не порождаете некий новый последовательный контейнер со специфичным поведением, а делаете какой-то менеджер настроек Вашего приложения. Менеджер по своей сути - явно не список, и как он хранит там у себя внутри настройки - сугубо его дело, то есть то, что называется деталями реализации. Завтра потребуется (заказчик так захотел, например, API какое-то там прикрутить надо) хранить настройки в древовидной структуре - менеджер настроек станет деревом? А если в БД - ему что, датасетом стать прикажете?
ОтветитьУдалитьЧто касается вот этого:
>for i:=List.Count-1 downto 0 do
>List[i].Free
и того, что список что-то там заметит - так извините: ничего список не заметит, просто потому, что вы с ним ничего здесь не делаете. Во-первых, нужно понимать, что есть такое TList. Это классовая обертка над массивом указателей. Неважно на что - void * некий. Соответственно, TList хранит указатели, и ни о чем другом, ни о каких этих ваших объектах или чем-то там еще, на чем вы пытаетесь позвать метод Free, он понятия не имеет. А во-вторых, посмотрите код Classes.pas. По своей сути, TList - он типа сильно упрощенного std::vector. Размер памяти, выделенной для хранения его элементов, изменяется только в методе TList.SetCapacity, который зовется при добавлении элементов в список и при вызове метода Clear. Он не вызывается ни при Remove, ни при Delete.
Хотите "сжимать" ваш список при убиении объектов, которые Вы туда положили - занулите указатели на них в списке и позовите метод Pack, он для того и сделан. Вот только размер памяти, занимаемой списком при этом не уменьшится по причинам, выше уже упомянутым.