В Delphi (и, вероятно, много других языков) есть помощники класса. Они обеспечивают способ добавления дополнительных методов к существующему классу. Без создания подкласса.
Итак, что полезно для помощников классов?
В Delphi (и, вероятно, много других языков) есть помощники класса. Они обеспечивают способ добавления дополнительных методов к существующему классу. Без создания подкласса.
Итак, что полезно для помощников классов?
Я использую их:
Чтобы добавить методы в класс TStrings, я могу использовать те же методы в мои производные списки и в TStringList.
TGpStringListHelper = class helper for TStringList
public
function Last: string;
function Contains(const s: string): boolean;
function FetchObject(const s: string): TObject;
procedure Sort;
procedure Remove(const s: string);
end; { TGpStringListHelper }
Чтобы упростить доступ к полям записи и удалить кастинг.
Например, если вам нужна дополнительная функциональность для существующего класса экземпляра, и по какой-то причине вы не можете изменить существующий источник. Вы можете создать помощник класса, чтобы добавить эту функциональность.
Пример:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
Каждый раз мы теперь используем экземпляр (подкласс) TStrings и TStringsHelper находится в пределах области действия. У нас есть доступ к методу IsEmpty.
Пример:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
Примечания:
Это очень похоже на методы расширения в С# 3 (и VB9). Лучшее использование, которое я видел для них, - это расширения для IEnumerable<T>
(и IQueryable<T>
), которые позволяют LINQ работать с произвольными последовательностями:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(или что-то еще, конечно). Все это выполнимо, поскольку методы расширения позволяют эффективно объединять вызовы статическим методам, которые принимают тот же тип, что и они.
Они очень полезны для плагинов. Например, пусть ваш проект определяет определенную структуру данных и сохраняет ее на диске определенным образом. Но тогда какая-то другая программа делает что-то очень похожее, но файл данных отличается. Но вы не хотите раздувать ваш EXE с кучей кода импорта для функции, которую многие ваши пользователи не будут использовать. Вы можете использовать платформу плагина и поместить импортеров в плагин, который будет работать следующим образом:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
И затем определите конвертер. Одно предостережение: помощник класса не является другом класса. Этот метод будет работать только в том случае, если можно полностью настроить новый объект TMyClass с помощью своих общедоступных методов и свойств. Но если вы можете, он работает очень хорошо.
В первый раз, когда я помню, что вы испытывали то, что вы называете "помощниками класса", было при изучении Objective C. Cocoa (инфраструктура Apple Objective C) использует так называемые "категории".
Категория позволяет расширить существующий класс, добавив собственные методы без подкласса. Фактически Cocoa рекомендует вам избегать подклассов, когда это возможно. Часто это имеет смысл для подкласса, но часто его можно избежать с помощью категорий.
Хорошим примером использования категории в Cocoa является то, что называется "Key Value Code (KVC)" и "Key Value Observing (KVO)".
Эта система реализована с использованием двух категорий (NSKeyValueCoding и NSKeyValueObserving). Эти категории определяют и реализуют методы, которые могут быть добавлены в любой класс, который вы хотите. Например, Cocoa добавляет "соответствие" KVC/KVO с помощью этих категорий для добавления методов в NSArray, таких как:
- (id)valueForKey:(NSString *)key
Класс NSArray не имеет ни декларации, ни реализации этого метода. Однако, используя категорию. Вы можете вызвать этот метод для любого класса NSArray. Вам не требуется подкласс NSArray для получения соответствия KVC/KVO.
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:@"name"]; // Call a method defined in the category
Используя этот метод, вы легко можете добавить поддержку KVC/KVO в свои собственные классы. Интерфейсы Java позволяют добавлять объявления методов, но категории позволяют также добавлять фактические реализации в существующие классы.
Как показывает GameCat, TStrings - хороший кандидат, чтобы избежать ввода текста:
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst['MyName'] := TMyObject.Create;
...
lst['MyName'].DoSomething;
...
end;
Вам когда-нибудь приходилось обращаться к многострочным строкам в реестре?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, nil, @DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, PByte(Buf), @DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;
Я бы не рекомендовал их использовать, так как я прочитал этот комментарий:
"Самая большая проблема с классом помощники, из p.o.v их использования в ваших собственных приложениях, это факт что только один помощник класса для данного класс может быть в любой момент".... "То есть, если у вас есть два помощника в рамках, будет признан только ОДИН компилятором. Вы не получите никакой предупреждения или даже намеки на любые другие которые могут быть скрыты.
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
Я видел, как они использовались для создания доступных методов класса, согласованных между классами: Добавление Open/Close и Show/Hide ко всем классам данного типа, а не только к активным и видимым свойствам.