© 2005 Александр Калабухов
При написании кода программист очень часто использует локальные переменные. И не менее часто эти переменные содержат экземпляры классов. Delphi в таком случае проигрывает Java и C++ в удобстве, т.к. в первом программист обязан самостоятельно уничтожить созданные объекты. Приходится наполнять функции бесконечными секциями try finally. А такой код довольно плохо читается, логика забивается деталями реализации. Чтобы отделить логику от обработки уничтожения временных объектов приходится писать слой промежуточных функций. А код все запутывается и запутывается...
Что долго говорить - рассмотрим пример!
Интересная задача - сортировка строк. Делфист решает сортировать строки TStringList'ом.
function SortStrings(const aFileName: String);
var
TmpStringList: TStringList;
begin
TmpStringList := TStringList.Create;
try
// Читаем строки из файла
TmpStringList.LoadFromFile(aFileName);
// Наполняем StringList строками
TmpStringList.Sorted := True;
// Сохраняем в файл
TmpStringList.SaveToFile(aFileName);
finally
TmpStringList.Free;
end;
end;
Далее. Системный архитектор обругал делфиста свиньей, и, для читаемости кода была создана еще одну функция.
function SortStrings(const aFileName: String);
var
TmpStringList: TStringList;
begin
TmpStringList := TStringList.Create;
try
InternalSortStrings(TmpStringList, aFileName);
finally
TmpStringList.Free;
end;
end;
function InternalSortStrings(StringList: TStringList; const aFileName: String);
var
TmpStringList: TStringList;
begin
// Читаем строки из файла
StringList.LoadFromFile(aFileName);
// Наполняем StringList строками
StringList.Sorted := True;
// Сохраняем в файл
StringList.SaveToFile(aFileName);
end;
Логика сортировки ушла в InternalSortStrings, а в SortStrings осталась подготовка и обработка ошибок. Код стал более прозрачным, но две функции тяжелее читать, чем одну!
К счастью в Delphi есть отличное лекарство от таких проблем. И называется оно интерфейсы. Что они дают? Подсчет ссылок средствами компилятора. Когда экземпляр класса, реализующего какой-нибудь интерфейс, станет "никому не нужен", он автоматически уничтожится.
Вот этой особенностью мы и воспользуемся для упрощения кода.
Базовые классы Delphi, такие как TList, TStream или TComponent не реализуют интерфейсов и не являются потомками TinterfacedObject. Соответсвенно, на самоубийство они не пойдут. Значит, необходимо написать свой класс – обертку над базовыми классами. Начнем прямо с TObject. Перво-наперво, опишем нужный интерфейс.
ISmartObject = interface
function GetObject: TObject;
end;
Наш умный объект должен быть контейнером для обыкновенных "глупых" объектов. Для этих целей объявлена функция GetObject.
Теперь напишем класс, реализующий наш интерфейс.
TSmartObject = class(TInterfacedObject, ISmartObject)
private
FObject: TObject;
protected
function GetObject: TObject;
public
constructor Create(aObject: TObject);
destructor Destroy; override;
end;
{TSmartObject}
constructor TSmartObject.Create(aObject: TObject);
begin
inherited Create;
FObject := aObject;
end;
destructor TSmartObject.Destroy;
begin
if Assigned(FObject) then
FObject.Free;
inherited;
end;
function TSmartObject.GetObject: TObject;
begin
Result := FObject;
end;
С таким классом уже можно упростить код. Например, так:
function SortStrings(const aFileName: String);
var
TmpStringList: TStringList;
begin
with TSmartObject.Create(TStringList.Create) as ISmartObject do
TmpStringList:= TStringList(GetObject);
{
TmpStringList := TStringList.Create;
try
}
// Читаем строки из файла
TmpStringList.LoadFromFile(aFileName);
// Наполняем StringList строками
TmpStringList.Sorted := True;
// Сохраняем в файл
TmpStringList.SaveToFile(aFileName);
{
finally
TmpStringList.Free;
end;
}
end;
Несколько некрасиво выглядит преобразование типа, но от него никуда не деться. Хотя для стандартных классов VCL можно заготовить более удобные обертки. Обернем, например, мой любимый TStringList:
ISmartStringList = interface
function GetObject: TstringList;
end;
TSmartStringList = class(TInterfacedObject, ISmartStringList)
private
FStringList: TStringList;
protected
function GetObject: TStringList;
public
constructor Create(aStringList: TStringList);
destructor Destroy; override;
end;
{ TSmartStringList }
constructor TSmartStringList.Create(aStringList: TStringList);
begin
inherited Create;
FStringList := aStringList;
end;
destructor TSmartStringList.Destroy;
begin
if Assigned(FStringList) then
FStringList.Free;
inherited;
end;
function TSmartStringList.GetObject: TStringList;
begin
Result := FStringList;
end;
Для пущего удобства обернем создание экземпляра нашего "умного" объекта функцией.
function IObject(aObject: TObject): ISmartObject;
begin
Result := TSmartObject.Create(aObject) as IsmartObject;
end;
function IStringList(aStringList: TStringList): ISmartStringList;
begin
Result := TSmartStringList.Create(aStringList) as ISmartStringList;
end;
Теперь функция сортировки строк выглядит совсем красиво:
function SortStrings(const aFileName: String);
begin
with IStringList(TStringList.Create).GetObject do
begin
// Читаем строки из файла
LoadFromFile(aFileName);
// Сортируем
Sorted := True;
// Сохраняем в файл
SaveToFile(aFileName);
end;
end;
Результатом этой несложной работы становится значительное упрощение кода. Но как всегда не обойдется без ложки дегтя – размер программы немного возрастет, а скорострельность немного понизится.
Могу добавить, что остается написать модуль с "умными" интерфейсами всех необходимых в работе классов и юзать его на всю катушку.
Желающие могут скачать исходный код использованного в заметке юнита.
Copyright© 2005 Александр Калабухов Специально для Delphi Plus
Пожалуйста, оцените статью