Самоубийство объектов

или

Об одном способе упрощения кода

© 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


Пожалуйста, оцените статью
Отлично
Хорошо
Средне
Плохо
Очень плохо

Rambler's Top100