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;
Недостатки:
вы должны придерживаться тех же правил, которые применяются к помощникам - никаких данных о членах и т. д. (на самом деле это не недостаток, за исключением того, что компилятор не будет "напоминать вам").
Вы должны явно привести в действие, чтобы использовать свой помощник
Если вы используете помощника для предоставления защищенных элементов, вы должны объявить помощника в том же модуле, в котором вы его используете (если только вы не предоставляете открытый метод, который предоставляет необходимые защищенные элементы)
Преимущества:
Абсолютно НЕТ риска того, что ваш помощник сломается, если вы начнете использовать какой-то другой код, который "помогает" тому же базовому классу
Явное приведение типов ясно показывает в вашем "потребительском" коде, что вы работаете с классом способом, который непосредственно не поддерживается самим классом, вместо того, чтобы обманывать и скрывать этот факт за каким-то синтаксическим сахаром.
Он не такой "чистый", как помощник класса, но в этом случае "более чистый" подход на самом деле просто подметает беспорядок под ковриком, и если кто-то мешает ковру, вы в конечном итоге получаете больший беспорядок, чем вы начали.
В настоящее время я все еще использую 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 как об ошибке.