Фундаментальные алгоритмы и структуры данных в Delphi
Шрифт:
Правильно было бы организовать обратный цикл, который бы начал удалять элементы с конца списка. Так мы могли бы избежать ошибки.
Для освобождения всех элементов списка используется следующий код, а не вызов метода Delete для каждого элемента:
for i := 0 to pred(MyList.Count) do
TObject(MyList[i]).Free;
end;
Еще одной проблемой при использовании класса TList является создание производного класса. Если попытаться это сделать, можно столкнуться с разного рода проблемами, вызванными тем, что методы TList являются статическими, к тому же имеют приватные поля, которые не доступны, и т.д. Можно только
При первом написании предыдущего параграфа автор книги не знал, что компания Borland сделала с классом TList в версии Delphi 5. В Delphi 5 по каким-то непостижимым причинам было изменено функционирование класса TList с целью обеспечения поддержки нового производного класса - TObjectList.TObjectList предназначен для хранения экземпляров объектов. Он находится в модуле Contnrs, о котором мы поговорим чуть позже.
Что же изменилось? В версиях до Delphi 5 TList очищался путем освобождения внутреннего массива указателей, что было операцией класса O(1). Поскольку компания Borland хотела, чтобы класс TObjectList при определенных условиях мог освобождать содержащиеся в нем объекты, она для обеспечения такой функциональности изменила основной принцип работы TList. В Delphi, начиная с версии 5, и, конечно же, Kylix, класс TList очищается путем вызова для каждого элемента нового виртуального метода Notify. Метод TList.Notify не выполняет никаких операций, но метод TObjectList.Notify при удалении элементов из списка освобождает занимаемую ими память.
Вы можете спросить: "Ну и что?" Дело в том, что этот новый метод очистки содержимого класса TList принадлежит к операциям класса О(n). Таким образом, чем больше элементов в списке, тем больше времени потребуется на его очистку. По сравнению с предыдущими версиями TList, новая версия стала работать гораздо медленнее. Каждый экземпляр каждого класса, использующего TList, теперь будет работать медленнее. И помните, единственной причиной снижения быстродействия стало нежелание компании Borland воспользоваться делегированием, вместо наследования. По мнению компании, было намного удобнее изменить стандартный класс.
И что еще хуже с точки зрения объектно-ориентированного программирования, мы получили ситуацию, когда для поддержки производного класса был изменен родительский класс. TList не должен освобождать свои элементы - это стало правилом еще с версии Delphi1. Тем не менее, он был изменен для того, чтобы такая возможность поддерживалась его дочерними классами (а фактически только одним классом из VCL Delphi 5 - классом TObjectList).
Денни Торп (Denny Thorpe), один из самых толковых разработчиков в отделе научных исследований компании Borland, в своей книге "Разработка компонент Delphi" (Delphi Component Design) [23] сказал следующее:
"TList - это рабочая лошадка, а не порождающий класс... При необходимости использования списка создайте простой интерфейсный класс (порожденный от TObject, а не от TList), который будет иметь только нужные вам эквиваленты свойств и функций TList, и возвращаемые типы значений функций и параметров методов которого соответствуют типу хранящихся в списке данных. В таком случае интерфейсный класс будет содержать в себе класс TList и использовать его для хранения данных. Реализуйте функции и методы доступа к свойствам этого интерфейсного класса в виде однострочных вызовов соответствующих методов или свойств внутреннего класса TList с применением
соответствующих преобразований типов".Очень жаль, что книга Денни Торпа не внесена в перечень книг, обязательных для прочтения в отделе научных исследований компании Borland.
Класс TtdObjectList
А сейчас мы создадим новый класс списка, который работает как TList, но имеет два отличия: он хранит экземпляры некоторого класса (или его дочерних классов) и при необходимости уничтожает все содержащиеся в нем объекты. Другими словами, это будет специализированный список, в котором не будет двух описанных в предыдущем разделе недостатков. Назовем наш класс TtdObjectList. Он отличается от класса TObjectList в Delphi 5 и более поздних версиях тем, что будет безопасным к типам.
Он не будет дочерним классом TList. Конечно, в нем будут содержаться те же методы, но их реализация будет основана на делегировании к методам с теми же именами внутреннего класса TList.
Класс TtdObjectList имеет один новый очень важный атрибут - владение данными. Это класс будет функционировать либо точно так же, как TList, т.е. при уничтожении его элементы не будут освобождаться (он не владеет данными), либо будет иметь полный контроль над своими элементами и при необходимости будет их удалять (он владеет данными). Установка атрибута владения данными выполняется при создании экземпляра класса TtdObjectList, и после этого уже не будет возможности изменить тип владения данными.
Кроме того, класс будет обеспечивать безопасность к типам (type safety). При создании экземпляра класса, необходимо указывать какой тип (или класс) объектов будет в нем храниться. Во время добавления или вставки нового элемента специальный метод будет проверять соответствие типа нового объекта объявленному типу элементов списка.
Интерфейс класса TtdObjectList напоминает интерфейс класса TList. В нем не реализован метод Pack, поскольку в список будут добавляться только объекты, не равные nil. Подробное описание метода Sort будет приведено в главе 5.
Листинг 2.10. Объявление класса TtdObjectList
TtdObjectList = class private
FClass : TClass;
FDataOwner : boolean;
FList : TList;
FName : TtdNameString;
protected
function olGetCapacity : integer;
function olGetCount : integer;
function olGetItem(aIndex : integer): TObject;
procedure olSetCapacity(aCapacity : integer);
procedure olSetCount(aCount : integer);
procedure olSetItem(aIndex : integer; aItem : TObject);
procedure olError(aErrorCode : integer; const aMethodName : TtdNameString; aIndex : integer);
public
constructor Create(aClass : TClass;
aDataOwner : boolean);
destructor Destroy; override;
function Add(aItem : TObject): integer;
procedure Clear;
procedure Delete(aIndex : integer);
procedure Exchange(aIndex1, aIndex2 : integer);
function First : TObject;
function IndexOf(aItem : TObject): integer;
procedure Insert(aIndex : integer; aItem : TObject);
function Last : TObject;
procedure Move(aCurIndex, aNewIndex : integer);
function Remove(aItem : TObject): integer;
procedure Sort(aCompare : TtdCompareFunc);
property Capacity : integer read olGetCapacity write olSetCapacity;
property Count : integer read olGetCount write olSetCount;
property DataOwner : boolean read FDataOwner;
property Items[Index : integer] : TObject read olGetItem write olSetItem; default;
property List : TList read FList;