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;
笔记:
这听起来很像 C#3(和 VB9)中的扩展方法。我为它们看到的最好的用途是对IEnumerable<T>
(and 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 中使用类别的一个很好的例子是所谓的“键值代码 (KVC)”和“键值观察 (KVO)”。
该系统使用两个类别(NSKeyValueCoding 和 NSKeyValueObserving)实现。这些类别定义并实现了可以添加到您想要的任何类的方法。例如 Cocoa 通过使用这些类别向 NSArray 添加方法来向 KVC/KVO 添加“一致性”,例如:
- (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;
我不建议使用它们,因为我阅读了以下评论:
“类助手的最大问题,从在你自己的应用程序中使用它们的观点来看,是一个给定类的唯一类助手可能在任何时候都在范围内。” ... “也就是说,如果你在范围内有两个助手,编译器只会识别一个。你不会收到任何警告,甚至不会提示任何其他可能隐藏的助手。”
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
我已经看到它们用于使可用的类方法在类之间保持一致:将 Open/Close 和 Show/Hide 添加到给定“类型”的所有类,而不仅仅是 Active 和 Visible 属性。
如果Dephi 支持扩展方法,我想要的一种用途是:
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