Class Helper для общего класса? - PullRequest
8 голосов
/ 21 октября 2009

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

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

не работает и

не работает
TQueueHelper = class helper of TQueue
  ...
end;

Ответы [ 3 ]

13 голосов
/ 21 октября 2009

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

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

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

TFooHelper = class helper for TFoo
  procedure MyHelperMethod;
end;

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

TFooHelper = class(TFoo)
  procedure MyHelperMethod;
end;

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

   TFooHelper(someFoo).MyHelperMethod;

Downsides:

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

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

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

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

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

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

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

12 голосов
/ 16 сентября 2011

В настоящее время я все еще использую 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.
  • Вы можете создавать дополнительные декораторы, которые добавляют больше функциональности и комбинируют декораторы различными способами. Вы даже можете использовать его для симуляции множественного наследования, хотя интерфейсы все еще проще.

Недостатки

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

Примечание на стороне

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

7 голосов
/ 21 октября 2009

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

...