37

Delphi(可能还有很多其他语言)有类助手。这些提供了一种向现有类添加额外方法的方法。无需创建子类。

那么,类助手有什么用处呢?

4

9 回答 9

34

我正在使用它们:

  • 枚举器插入到不实现它们的 VCL 类中。
  • 增强VCL 类。
  • 将方法添加到 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 }
    
  • 简化对记录字段的访问并删除强制转换

于 2008-10-31T13:30:44.677 回答
14

起初,我对班级助手持怀疑态度。但后来我读了一篇有趣的博客文章,现在我确信它们确实很有用。

例如,如果您想要现有实例类的额外功能并且由于某种原因您无法更改现有源。您可以创建一个类助手来添加此功能。

例子:

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;

笔记:

  • 类助手可以存储在一个单独的单元中,因此您可以添加自己的漂亮类助手。确保为这些单元起一个易于记忆的名称,例如 ClassesHelpers 用于 Classes 单元的助手。
  • 还有记录助手。
  • 如果范围内有多个类助手,预计会出现一些问题,只能使用一个助手。
于 2008-10-31T13:07:22.867 回答
6

这听起来很像 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);

(或其他,当然)。所有这些都是可行的,因为扩展方法允许您有效地将对静态方法的调用链接在一起,这些静态方法在返回时采用相同的类型。

于 2008-10-31T13:18:30.160 回答
4

它们对于插件非常有用。例如,假设您的项目定义了某种数据结构,并以某种方式保存到磁盘。但是其他一些程序做了非常相似的事情,但是数据文件不同。但是你不想用一堆导入代码来让你的 EXE 膨胀,因为你的很多用户都不需要使用这个功能。您可以使用插件框架并将导入器放入一个可以像这样工作的插件中:

type
   TCompetitionToMyClass = class helper for TMyClass
   public
      constructor Convert(base: TCompetition);
   end;

然后定义转换器。一个警告:班级助手不是班级朋友。这种技术只有在可以通过其公共方法和属性完全设置一个新的 TMyClass 对象时才有效。但是,如果可以,它会非常有效。

于 2008-10-31T20:37:49.713 回答
3

我记得第一次遇到您所说的“类助手”是在学习 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 接口允许您添加方法声明,但类别还允许您将实际实现添加到现有类。

于 2008-10-31T14:10:26.873 回答
3

正如 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;
于 2008-10-31T14:27:36.230 回答
3

我不建议使用它们,因为我阅读了以下评论:

“类助手的最大问题,从在你自己的应用程序中使用它们的观点来看,是一个给定类的唯一类助手可能在任何时候都在范围内。” ... “也就是说,如果你在范围内有两个助手,编译器只会识别一个。你不会收到任何警告,甚至不会提示任何其他可能隐藏的助手。”

http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html

于 2009-07-04T05:57:25.870 回答
0

我已经看到它们用于使可用的类方法在类之间保持一致:将 Open/Close 和 Show/Hide 添加到给定“类型”的所有类,而不仅仅是 Active 和 Visible 属性。

于 2009-01-27T11:32:59.347 回答
0

如果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
于 2021-11-03T15:26:39.730 回答