Код против интерфейса с TStrings и TStringList - PullRequest
7 голосов
/ 01 февраля 2012

Я с интересом читаю блог Ника Ходжеса на Почему вы должны использовать интерфейсы и так как я уже влюблен в интерфейсы на более высоком уровне в моем коде, я решил посмотреть, как я могу расширить это до довольно низких уровней, и выяснить, какая поддержка для этого существовала в классах VCL.

Обычная конструкция, которая мне нужна, это сделать что-то простое с TStringList, например, этот код для загрузки небольшого списка текстовых файлов в текстовую строку с запятой:

var
  MyList : TStrings;
  sCommaText : string;
begin
  MyList := TStringList.Create;
  try
    MyList.LoadFromFile( 'c:\temp\somefile.txt' );
    sCommaText := MyList.CommaText;

    // ... do something with sCommaText.....

  finally
    MyList.Free;
  end;
end;

Было бы неплохо упростить, если бы я мог писать с использованием MyList в качестве интерфейса - это избавило бы от try-finally и улучшило бы удобочитаемость:

var
  MyList : IStrings;
         //^^^^^^^
  sCommaText : string;
begin
  MyList := TStringList.Create;
  MyList.LoadFromFile( 'c:\temp\somefile.txt' );
  sCommaText := MyList.CommaText;

  // ... do something with sCommaText.....

end;

Хотя я не вижу определенных строк IStrings - конечно, не в Classes.pas, хотя есть ссылки на него в связи с OLE-программированием онлайн. Это существует? Это действительное упрощение? Я использую Delphi XE2.

Ответы [ 2 ]

6 голосов
/ 01 февраля 2012

В RTL / VCL нет интерфейса, который бы делал то, что вы хотите (предоставьте тот же интерфейс, что и TStrings). Если вы хотите использовать такую ​​вещь, вам придется изобретать ее самостоятельно.

Вы бы реализовали его с помощью обёртки, подобной этой:

type
  IStrings = interface
    function Add(const S: string): Integer;
  end;

  TIStrings = class(TInterfacedObject, IStrings)
  private
    FStrings: TStrings;
  public
    constructor Create(Strings: TStrings);
    destructor Destroy; override;
    function Add(const S: string): Integer;
  end;

constructor TIStrings.Create(Strings: TStrings);
begin
  inherited Create;
  FStrings := Strings;
end;

destructor TIStrings.Destroy;
begin
  FStrings.Free; // don't use FreeAndNil because Nick might see this code ;-)
  inherited;
end;

function TIStrings.Add(const S: string): Integer;
begin
  Result := FStrings.Add(S);
end;

Естественно, вы бы завернули остальную часть интерфейса TStrings в настоящий класс. Сделайте это с помощью класса-обёртки, как этот, чтобы вы могли обернуть любой тип TStrings, просто имея доступ к его экземпляру.

Используйте это так:

var
  MyList : IStrings;
....
MyList := TIStrings.Create(TStringList.Create);

Вы можете предпочесть добавить вспомогательную функцию, которая фактически выполняет грязную работу по вызову TIStrings.Create.

Обратите внимание, что проблема может быть в жизни Вы можете захотеть вариант этой оболочки, который не берет на себя управление временем жизни базового экземпляра TStrings. Это может быть организовано с помощью параметра конструктора TIStrings.


Сам, я думаю, что это интересный мысленный эксперимент, но не очень разумный подход. Класс TStrings является абстрактным классом, который обладает практически всеми преимуществами интерфейсов. Я не вижу реальных недостатков в использовании его как есть.

4 голосов
/ 01 февраля 2012

Поскольку TStrings является абстрактным классом, его интерфейсная версия не может обеспечить много. Любой разработчик этого интерфейса, несомненно, будет потомком TStrings, потому что никто не захочет повторно реализовать все то, что делает TStrings. Я вижу две причины, почему нужен интерфейс TStrings:

  1. Автоматическая очистка ресурса . Для этого вам не нужен специфичный TStrings интерфейс. Вместо этого используйте интерфейс ISafeGuard из JCL. Вот пример:

    var
      G: ISafeGuard;
      MyList: TStrings;
      sCommaText: string;
    begin
      MyList := TStrings(Guard(TStringList.Create, G));
    
      MyList.LoadFromFile('c:\temp\somefile.txt');
      sCommaText := MyList.CommaText;
    
      // ... do something with sCommaText.....
    end;
    

    Чтобы защитить несколько объектов, которые должны иметь одинаковое время жизни, используйте IMultiSafeGuard.

  2. Взаимодействие с внешними модулями. Для этого IStrings. Delphi реализует его с помощью класса TStringsAdapter, который возвращается при вызове GetOleStrings для существующего потомка TStrings. Используйте это, когда у вас есть список строк, и вам нужно предоставить доступ к другому модулю, который ожидает интерфейсы IStrings или IEnumString. Эти интерфейсы неудобны для использования в противном случае - ни один не обеспечивает все то, что делает TStrings - поэтому не используйте их, если вам не нужно.

    Если внешний модуль, с которым вы работаете, это то, что вы можете гарантированно всегда будет компилироваться с той же версией Delphi, с которой скомпилирован ваш модуль, тогда вам следует использовать пакеты времени выполнения и передать TStrings потомки напрямую. Общий пакет позволяет обоим модулям использовать одно и то же определение класса, а управление памятью значительно упрощено.

...