【问题标题】:What are good uses for class helpers?类助手有什么好的用途?
【发布时间】:2010-09-20 04:25:43
【问题描述】:

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

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

【问题讨论】:

    标签: delphi class helper


    【解决方案1】:

    我正在使用它们:

    • insert enumerators 放入未实现它们的 VCL 类中。
    • enhance VCL 类。
    • 向 TStrings 类添加方法,以便我可以在 my derived lists 和 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 }
      
    • 为了简化对记录字段和remove casting的访问。

    【讨论】:

      【解决方案2】:

      起初我对班级助手持怀疑态度。但后来我读到了一个有趣的blog entry,现在我确信它们确实有用。

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

      示例:

      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 单元的助手。
      • 还有记录助手。
      • 如果范围内有多个类助手,预计会出现一些问题,只能使用一个助手。

      【讨论】:

      • 您是否阅读过评论:“从在您自己的应用程序中使用它们的观点来看,类助手的最大问题是,给定类的范围内可能只有一个类助手随时。” ... “也就是说,如果你在范围内有两个助手,编译器只会识别一个。你不会收到任何警告,甚至不会提示任何其他可能隐藏的助手。”
      • CLASS 助手可以继承自其他 CLASS 助手,因此您可以 - 实际上 - 有多个 CLASS 助手同时处于活动状态(但它们必须相互了解,因此您只能拥有一个CLASS 助手的连续“字符串”处于活动状态,即如果你有 Helper1,Helper2 从 Helper1 继承,Helper3 从 Helper2 继承,那么你不能只让 Helper3 处于活动状态(没有一些条件编译指令),但必须同时拥有这三个范围内的助手 - 您可以只有 Helper1,只有 Helper1+Helper2 或同时激活所有三个 - 没有 Helper1+Helper3)。
      • “类助手可以相互继承”——这使类助手的全部优点失效。
      【解决方案3】:

      这听起来很像 C#3(和 VB9)中的扩展方法。我见过它们的最佳用途是对IEnumerable<T>(和IQueryable<T>)的扩展,它可以让LINQ 处理任意序列:

      var query = someOriginalSequence.Where(person => person.Age > 18)
                                      .OrderBy(person => person.Name)
                                      .Select(person => person.Job);
      

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

      【讨论】:

      • 附带说明,Delphi 自 2000 年以来就拥有类助手,我们拥有该概念的专利。 Nick Hodges Delphi 产品经理 Embarcadero Technologies
      • @Nick:你说的是哪个版本的 Delphi,自 2000 年以来就有类助手?我只记得在 Delphi 2007 中使用它们进行非破坏性发布时阅读过它们。那是您声称 Delphi 拥有它们的那一年之后的 6 年。
      • 在 Delphi 8(Delphi for .NET 的第一个版本)中将类助手添加到 Delphi。
      • @NickHodges 您是否还申请了无法同时激活多个助手的专利?
      【解决方案4】:

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

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

      然后定义转换器。一个警告:类helper 不是类friend。这种技术只有在可以通过其公共方法和属性完全设置一个新的 TMyClass 对象时才有效。但如果可以的话,它真的很好用。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          正如 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;
          

          【讨论】:

          • 哇,覆盖“默认”!极好的。并且可能是奇怪错误的重要来源:)
          • 同意,这有点不对劲。但我主要在狭窄范围内使用类助手,所以这不是问题 - 到目前为止......
          【解决方案7】:

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

          “类最大的问题 助手,来自使用它们的 p.o.v 在您自己的应用程序中,这是事实 给定的只有一个类助手 课程可能随时都在范围内。” ……“也就是说,如果你有两个帮手 在范围内,只有一个会被识别 由编译器。你不会得到任何 警告甚至暗示任何其他 可能隐藏的助手。”

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

          【讨论】:

          • 我已阅读该评论。但这不是不使用它们的理由。就像任何其他工具一样小心使用它们。
          • @GamecatisToonKrijthe 甚至没有提示,这意味着开发人员现在必须扫描第三方库的所有来源(不仅一次,而且每次更新)以检测“冲突助手”...
          • 这是正确答案。帮助程序不是 Delphi 版本的 C# 扩展方法。助手不是通用的编程解决方案,不应在任何新代码中使用。如果你试图在一个类中已经存在的地方声明一个帮助器(例如TGuidHelper),那么你会破坏现有的代码。如果 Delphi 支持扩展方法,我会LOVE;这将是一个 EXTRAORDINARILY 有用的语言功能。但助手不是那个特性。
          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

            如果 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-09-20
              • 2011-01-26
              • 1970-01-01
              • 2011-05-19
              • 1970-01-01
              • 1970-01-01
              • 2011-03-03
              相关资源
              最近更新 更多