2005. december 30., péntek

Subclassing a versatile TList

Problem/Question/Abstract:

You can use a TList almost for everything, so an own class leads to better design and maintainability therefore the article shows how and why.

Answer:

A certain view is that the TList class in Object Pascal (OP) is not a class from which we can descend, so the choice lies between subclassing (inheritance) or delegation (means create a separate class which holds the TList instance). But you can combine the two OO-technologies, especially you have multiple objects to store:

Subclass the TList that exposes only function equivalents of TList
Create a separate class that uses a TList instance

Some Advantages and Tricks of TList:

TList, which stores an dynamic array of pointers, is often used to maintain lists of objects or records. TList introduces properties and methods to

Add or delete the objects in the list.
Rearrange the objects in the list.
Locate and access objects in the list.
Sort the objects in the list.

The Items of a TList are numbered from 0 to Count-1, that means zero based. Above D5 and Kylix, Borland changed the operation of TList with the introduction of a new descendant called TObjectList. They changed only the mechanism of freeing objects in a TList.
If the OwnsObjects property of a TObjectList is set to True (the default), TObjectList controls the memory of its objects (by a new virtual method Notify), freeing an object when its index is reassigned or or when the TObjectList instance is itself destroyed, but the more items in the TList, the longer it takes. The worse is that a TList gets slower, so write always like in the following example your own Free-method (as it was with pre-Delphi 5 TList)!

var
Childs: TSubTList;

for i := 0 to Childs.count - 1 do
BusinessClass(Childs[i]).Free;
Childs.Free

BusinessClass(Childs[i]).Free calls every object on the list and frees the memory of every object or record that we add on the list.
Then Child.Free calls Destroy and then it calls Clear of TList but Clear only empties the Items array and set the Count to 0. Clear frees the memory used to store the Items array and sets the Capacity to 0. Be care about Delete, Delete does not free any memory associated with the item.

Gain speed with TList

The TList Sort mechanism is implemented with a quicksort algorithm, means we're fast enough, but how about the access?
The normal way of accessing an object or item in a TList is the Items property in a default manner like theList[i]. The performance problem is the reading or writing, cause the compiler in OP inserts code to call getter or setter-methods, like theList.get[i] which checks the index between 0 and Count -1. If we want gain speed and get rid of the getter/setter we can call direct a variable of type PPointerList (named List), but no validation takes place.

Childs.List^[i];

You then takes responsability of making sure reading or writing can't be beyond the ends of an array of the TList.

Example

The subclassing is like a wrapper class with simle one-line calls to the corresponding methods of the inherited TList without typecasts. The example shows how to add a record but with an object you have to change only the type and instead of Dispose use Free.
The Method Add always inserts the Item pointer at the end of the Items array, even if the Items array contains nil pointers:

var
Childs: TSubTList; //or TBrokerList
Childs.Add(BusinessClass.create(self));

Not all of the entries in the Items array need to contain references to objects. Some of the entries may be NIL pointers. To remove the NIL pointers and reduce the size of the Items array to the number of objects, call the Pack method.

type
TBrokerRec = record
intVal: integer;
strVal: string;
ptrStr: pChar;
end;
PBrok = ^TBrokerRec;

TBrokerList = class(TList)
protected
procedure freeElement(elem: PBrok);
function GetItems(Index: Integer): PBrok;
procedure SetItems(Index: Integer; item: PBrok);
public
destructor destroy; override;
function Add(Item: PBrok): Integer;
procedure Delete(index: integer);
function First: PBrok;
function indexOf(item: PBrok): Integer;
procedure Insert(index: integer; item: PBrok);
function Last: PBrok;
procedure pClear;
function Remove(item: PBrok): Integer;
property Items[Index: Integer]: PBrok read GetItems write SetItems;
end;

TBrokerList

destructor TBrokerList.destroy;
begin
clear;
inherited Destroy;
end;

function TBrokerList.Add(Item: PBrok): Integer;
begin
result := inherited Add(Item);
end;

procedure TBrokerList.Delete(index: integer);
begin
freeElement(items[index]);
inherited delete(index);
end;

function TBrokerList.First: PBrok;
begin
result := inherited First;
end;

procedure TBrokerList.freeElement(elem: PBrok);
begin
if elem <> nil then
dispose(elem);
end;

function TBrokerList.indexOf(item: PBrok): Integer;
begin
result := inherited indexOf(item);
end;

procedure TBrokerList.Insert(index: integer; item: PBrok);
begin
inherited insert(index, item);
end;

function TBrokerList.Last: PBrok;
begin
result := inherited Last;
end;

procedure TBrokerList.pClear; //instead of Free from outer class
var
x: Integer;
begin
for x := 0 to count - 1 do
freeElement(items[x]);
inherited clear;
end;

function TBrokerList.Remove(item: PBrok): Integer;
begin
result := indexOf(item);
if Result <> -1 then
delete(result);
end;

function TBrokerList.GetItems(Index: Integer): PBrok;
begin
result := inherited get(index);
end;

procedure TBrokerList.SetItems(Index: Integer; item: PBrok);
begin
inherited put(index, item);
end;


Nincsenek megjegyzések:

Megjegyzés küldése