Class Helper для общего класса?

Я использую Delphi 2009. Можно ли написать вспомогательный класс для универсального класса, то есть для TQueue . Очевидное

TQueueHelper <T> = class helper of TQueue <T>
  ...
end;

не работает и не работает

TQueueHelper = class helper of TQueue
  ...
end;

3 ответа

Решение

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

тем не менее, существует мнение - неправильное и опасное, на мой взгляд, - что это законный инструмент в универсальном "инструментарии". Я писал в блоге о том, почему это неправильно, и впоследствии о том, как можно каким-то образом смягчить опасности, следуя социально ответственному шаблону кодирования (хотя даже это не является пуленепробиваемым).

Вы можете достичь эффекта помощника по классу без каких-либо ошибок или ограничений или (что наиболее важно) рисков, используя жесткое приведение к "псевдо" классу, производному от класса, который вы пытаетесь расширить. т.е. вместо:

TFooHelper = class helper for TFoo
  procedure MyHelperMethod;
end;

использование

TFooHelper = class(TFoo)
  procedure MyHelperMethod;
end;

Как и в случае с "формальным" помощником, вы никогда не создаете экземпляр этого класса TFooHelper, вы используете его исключительно для изменения класса TFoo, за исключением того, что в этом случае вы должны быть явным. В вашем коде, когда вам нужно использовать какой-то экземпляр TFoo, используя ваши "вспомогательные" методы, вам нужно выполнить hard cast:

   TFooHelper(someFoo).MyHelperMethod;

Недостатки:

  1. вы должны придерживаться тех же правил, которые применяются к помощникам - никаких данных о членах и т. д. (на самом деле это не недостаток, за исключением того, что компилятор не будет "напоминать вам").

  2. Вы должны явно привести в действие, чтобы использовать свой помощник

  3. Если вы используете помощника для предоставления защищенных элементов, вы должны объявить помощника в том же модуле, в котором вы его используете (если только вы не предоставляете открытый метод, который предоставляет необходимые защищенные элементы)

Преимущества:

  1. Абсолютно НЕТ риска того, что ваш помощник сломается, если вы начнете использовать какой-то другой код, который "помогает" тому же базовому классу

  2. Явное приведение типов ясно показывает в вашем "потребительском" коде, что вы работаете с классом способом, который непосредственно не поддерживается самим классом, вместо того, чтобы обманывать и скрывать этот факт за каким-то синтаксическим сахаром.

Он не такой "чистый", как помощник класса, но в этом случае "более чистый" подход на самом деле просто подметает беспорядок под ковриком, и если кто-то мешает ковру, вы в конечном итоге получаете больший беспорядок, чем вы начали.

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

Классыперехватчиков

Классы-перехватчики - это классы, которым присвоено то же имя, что и классу, от которого они наследуют:

TList<T> = class(Generics.Collections.TList<T>)
public
  type
    TDynArray = array of T;
  function ToArray: TDynArray;
end;

function TList<T>.ToArray: TDynArray;
var
  I: Integer;
begin
  SetLength(Result, self.Count);
  for I := 0 to Self.Count - 1 do
  begin
    Result[I] := Self[I];
  end;
end;

Обратите внимание, что вам нужно использовать полное имя, Generics.Collections.TList<T> как предок. В противном случае вы получите E2086 Type '%s' is not completely defined,

Преимущество этого метода в том, что ваши расширения в основном прозрачны. Вы можете использовать экземпляры нового TList везде, где использовался оригинал.

У этой техники есть два недостатка:

  • Это может вызвать замешательство у других разработчиков, если они не знают, что вы переопределили знакомый класс.
  • Это не может быть использовано в запечатанном классе.

Путаница может быть смягчена путем тщательного присвоения имен единицам и избегания использования "оригинального" класса в том же месте, что и ваш класс-перехватчик. Запечатанные классы не являются большой проблемой в классах rtl/vcl, поставляемых Embarcadero. Я обнаружил только два запечатанных класса во всем дереве исходного кода: TGCHandleList(используется только в ныне несуществующей Delphi.NET) и TCharacter. Однако вы можете столкнуться с проблемами со сторонними библиотеками.

Образец Декоратора

Шаблон декоратора позволяет динамически расширять класс, оборачивая его другим классом, который наследует его открытый интерфейс:

TArrayDecorator<T> = class abstract(TList<T>)
public
  type
    TDynArray = array of T;
  function ToArray: TDynArray; virtual; abstract;
end;

TArrayList<T> = class(TArrayDecorator<T>)
private
  FList: TList<T>;
public
  constructor Create(List: TList<T>);
  function ToArray: TListDecorator<T>.TDynArray; override;
end;

function TMyList<T>.ToArray: TListDecorator<T>.TDynArray;
var
  I: Integer;
begin
  SetLength(Result, self.Count);
  for I := 0 to Self.Count - 1 do
  begin
    Result[I] := FList[I];
  end;
end;

Еще раз есть свои преимущества и недостатки.

преимущества

  • Вы можете отложить введение нового функционально до тех пор, пока оно действительно не понадобится. Нужно сбросить список в массив? Создайте новый TArrayList, передав любой TList или его потомка в качестве параметра в конструкторе. Когда вы закончите, просто откажитесь от TArrayList.
  • Вы можете создавать дополнительные декораторы, которые добавляют больше функциональности и по-разному комбинируют декораторы. Вы даже можете использовать его для симуляции множественного наследования, хотя интерфейсы все еще проще.

Недостатки

  • Это немного сложнее понять.
  • Применение нескольких декораторов к объекту может привести к многословным цепочкам конструктора.
  • Как и в случае с перехватчиками, вы не можете расширить запечатанный класс.

Примечание

Поэтому кажется, что если вы хотите сделать класс практически невозможным для расширения, сделайте его запечатанным родовым классом. Тогда помощники класса не могут прикоснуться к нему, и он не может быть унаследован от. О единственном оставленном варианте - завернуть его.

Насколько я могу судить, нет никакого способа поместить вспомогательный класс в общий класс и заставить его скомпилировать. Вы должны сообщить об этом в QC как об ошибке.

Другие вопросы по тегам