Delphi(以及可能许多其他语言)具有类助手。它们提供了一种向现有类添加额外方法的方法,而不必制作子类。
那么,类助手的好处是什么?
我正在使用它们:
To add methods to the TStrings class so I can use the same methods in my derived lists and in 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 }
To simplify access to record fields and remove casting.
一开始我对类助手有些怀疑。但是后来我读了一篇有趣的博客文章,现在我相信它们确实很有用。
例如,如果您想为现有实例类添加额外的功能,但由于某种原因无法更改现有源代码。您可以创建一个类助手来添加此功能。
示例:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
注意:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(当然,这只是示例)。所有这些都是可行的,因为扩展方法允许您有效地将对静态方法的调用链接在一起,这些方法接受与它们返回相同的类型。
TGuidHelper
),那么您将会破坏现有的代码。如果 Delphi 支持扩展方法,那将是一个非常有用的语言特性,我会非常喜欢它。但 helpers 并不是这个功能。 - Ian Boyd它们对于插件非常有用。例如,假设您的项目定义了某个数据结构,并以某种方式保存到磁盘中。但是,其他一些程序执行了非常类似的操作,但数据文件不同。但是,您不想在EXE中膨胀一堆导入代码,因为很多用户不需要使用此功能。您可以使用插件框架并将导入器放入一个插件中,该插件将像这样工作:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
然后定义转换器。有一个需要注意的地方:类“helper”不是类“friend”。只有当可以通过其公共方法和属性完全设置新的TMyClass对象时,此技术才能正常工作。但如果可以,它会非常有效。
- (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接口允许您添加方法声明,但是分类允许您还可以将实际实现添加到现有类中。
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;
TGuidHelper = class
public
class function IsEmpty(this Value: TGUID): Boolean;
end;
class function TGuidHelper(this Value: TGUID): Boolean;
begin
Result := (Value = TGuid.Empty);
end;
所以我可以调用 if customerGuid.IsEmpty then ...
。
另一个很好的例子是使用 IDataRecord
范例(我喜欢)从 XML 文档(或 JSON,如果你喜欢)中读取值:
orderGuid := xmlDocument.GetGuid('/Order/OrderID');
这比以下的方式要好得多:
var
node: IXMLDOMNode;
node := xmlDocument.selectSingleNode('/Order/OrderID');
if Assigned(node) then
orderID := StrToGuid(node.Text) //throw convert error on empty or invalid
else
orderID := TGuid.Empty; // "DBNull" becomes the null guid
我见过它们被用于使给定“类型”的所有类方法保持一致:将打开/关闭和显示/隐藏添加到所有类中,而不仅仅是活动和可见属性。
其他编程语言都有经过精心设计的类帮助器。
Delphi引入了类帮助器,仅仅是为了帮助Borland工程师解决Delphi和Delphi.net之间的兼容性问题。
它们从未被设计用于“用户”代码,并且自引入以来一直没有得到改进。如果开发框架(在框架内部私下使用,就像最初的.NET兼容性解决方案一样),它们可能会有所帮助;但是,将Delphi类帮助器与其他语言中的类帮助器等同起来,或者试图从其他语言中的示例中寻找Delphi中的用例,这是非常错误的。
直到今天,当前的Delphi文档对类和记录帮助器的描述如下:
它们不应被视为开发新代码时使用的设计工具
参考:https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Class_and_Record_Helpers_(Delphi)
因此,在Delphi中,“类帮助器的良好用途是什么”的答案非常简单: 只有一种安全的用法:只在实现和使用帮助器的单一代码库中的特定上下文扩展的效用和兴趣(详细示例:https://www.deltics.co.nz/blog/posts/683)。在uses
子句中添加一个单元或者仅仅改变单元的顺序,可能会意外地“隐藏”了你代码所需的帮助程序(你甚至可能不知道该帮助程序来自哪里)。
向已经存在于uses
列表中的单元添加一个帮助程序,可能会隐藏之前从另一个单元“导入”和使用的某些其他帮助程序。
由于这个次要问题,如果你无法重新排列uses
列表以使一个所需的帮助程序“可见”,或者你需要两个不相关的帮助程序(彼此不知晓因此无法相互“扩展”),那么就没有办法使用它!
值得强调的是,Delphi类帮助程序破坏其他人的代码的能力是一种几乎独一无二的糟糕特性。许多语言的许多语言特性可以被滥用以破坏自己的代码;但并不多能让你去破坏别人的代码!
在这里的各种文章中有更多详细信息:https://www.deltics.co.nz/blog/?s=class+helpers
特别是这个链接:https://www.deltics.co.nz/blog/posts/273/。