【问题标题】:Class Helper for generic class?泛型类的类助手?
【发布时间】:2009-10-21 11:27:53
【问题描述】:

我正在使用 Delphi 2009。是否可以为泛型类(即 TQueue )编写类助手。显而易见的

TQueueHelper <T> = class helper of TQueue <T>
  ...
end;

没有用,也没有

TQueueHelper = class helper of TQueue
  ...
end;

【问题讨论】:

    标签: delphi generics delphi-2009 class-helpers


    【解决方案1】:

    正如 Delphi 帮助中所述,类助手并非为通用用途而设计,因此被错误地认为存在许多限制甚至错误。

    尽管如此,有一种看法——在我看来是不正确和危险的——认为这些是通用“工具包”中的合法工具。我在博客上写过 why this is wrong 以及随后的博客,介绍了如何通过 following a socially responsible coding pattern 减轻危险​​(尽管这也不是防弹的)。

    您可以在没有任何这些错误或限制或(最重要的)风险的情况下通过对“伪”使用硬转换来实现类助手的大部分效果从您要扩展的类派生的类。即代替:

    TFooHelper = class helper for TFoo
      procedure MyHelperMethod;
    end;
    

    使用

    TFooHelper = class(TFoo)
      procedure MyHelperMethod;
    end;
    

    就像使用“正式”助手一样,你永远不会实例化这个 TFooHelper 类,你只使用它来改变 TFoo 类,除非在这种情况下你必须明确。在您的代码中,当您需要使用“帮助”方法使用 TFoo 的某些实例时,您必须进行硬转换:

       TFooHelper(someFoo).MyHelperMethod;
    

    缺点:

    1. 您必须遵守适用于帮助程序的相同规则 - 没有成员数据等(根本不是真正的缺点,只是编译器不会“提醒您”)。

      李>
    2. 您必须显式强制转换才能使用您的助手

    3. 如果使用帮助程序来公开受保护成员,则必须在使用它的同一单元中声明帮助程序(除非您公开公开所需的受保护成员的公共方法)

    优点:

    1. 如果您开始使用“帮助”同一基类的其他代码,您的助手绝对不会崩溃

    2. 显式类型转换在您的“消费者”代码中清楚地表明,您正在以类本身不直接支持的方式使用该类,而不是在一些语法糖后面捏造和隐藏该事实。

    它不像班级助手那样“干净”,但在这种情况下,“更干净”的方法实际上只是扫除地毯下的烂摊子,如果有人打扰了地毯,你最终会得到比开始时更大的烂摊子。

    【讨论】:

    • +1 有趣。也许我对他们太热情了。将您自己的辅助函数放入 VCL 中似乎非常优雅。不幸的是,演员阵容使其不那么优雅和可读性。
    • 对我来说,“可读性”同样重要——如果不是更多——能够清楚地看到正在发生的事情,因为它可以减少打字量。当我看到正在使用某个类“TxxxHelper”应用硬演员时,我就知道正在进行一些猴子生意。我绝对可能需要知道的猴子业务,如果不是现在,那么在将来我可能忘记了“MyMethod”不是它看起来的类的一部分并且无法弄清楚为什么其他一些代码尝试使用它不会编译(即,我在范围内没有合适的助手或被另一个“遮蔽”)。
    【解决方案2】:

    我目前仍在使用 Delphi 2009,所以我想我会添加一些其他方法来扩展泛型类。这些应该在较新版本的 Delphi 中同样有效。让我们看看将 ToArray 方法添加到 List 类会是什么样子。

    拦截器

    拦截器类是与它们继承的类同名的类:

    TList<T> = class(Generics.Collections.TList<T>)
    public
      type
        TDynArray = array of T;
      function ToArray: TDynArray;
    end;
    
    function TList<T>.ToArray: TDynArray;
    var
      I: Integer;
    begin
      SetLength(Result, self.Count);
      for I := 0 to Self.Count - 1 do
      begin
        Result[I] := Self[I];
      end;
    end;
    

    请注意,您需要使用完全限定名称 Generics.Collections.TList&lt;T&gt; 作为祖先。否则你会得到E2086 Type '%s' is not completely defined

    这种技术的优点是您的扩展大部分是透明的。您可以在使用原始 TList 的任何地方使用新 TList 的实例。

    这种技术有两个缺点:

    • 如果其他开发人员不知道您重新定义了熟悉的类,这可能会导致他们感到困惑。
    • 它不能用于密封类。

    谨慎的单元命名和避免在与拦截器类相同的位置使用“原始”类可以减轻混淆。在 Embarcadero 提供的 rtl/vcl 类中,密封类不是什么大问题。我只在整个源代码树中发现了两个密封类:TGCHandleList(仅在现已失效的 Delphi.NET 中使用)和 TCharacter。不过,您可能会遇到第三方库的问题。

    装饰器模式

    装饰器模式允许您通过用另一个继承其公共接口的类包装一个类来动态扩展它:

    TArrayDecorator<T> = class abstract(TList<T>)
    public
      type
        TDynArray = array of T;
      function ToArray: TDynArray; virtual; abstract;
    end;
    
    TArrayList<T> = class(TArrayDecorator<T>)
    private
      FList: TList<T>;
    public
      constructor Create(List: TList<T>);
      function ToArray: TListDecorator<T>.TDynArray; override;
    end;
    
    function TMyList<T>.ToArray: TListDecorator<T>.TDynArray;
    var
      I: Integer;
    begin
      SetLength(Result, self.Count);
      for I := 0 to Self.Count - 1 do
      begin
        Result[I] := FList[I];
      end;
    end;
    

    再一次有优点也有缺点。

    优势

    • 您可以推迟引入新功能,直到实际需要。需要将列表转储到数组中吗?构造一个新的 TArrayList,将任何 TList 或后代作为构造函数中的参数传递。完成后,只需丢弃 TArrayList。
    • 您可以创建额外的装饰器来添加更多功能并以不同的方式组合装饰器。您甚至可以使用它来模拟多重继承,尽管接口仍然更简单。

    缺点

    • 理解起来有点复杂。
    • 对一个对象应用多个装饰器会导致构造函数链冗长。
    • 与拦截器一样,您不能扩展密封类。

    旁注

    因此,如果你想让一个类几乎不可能扩展,就让它成为一个密封的泛型类。然后类助手不能接触它,也不能继承它。剩下的唯一选择就是包装它。

    【讨论】:

      【解决方案3】:

      据我所知,没有办法将类助手放在泛型类上并让它编译。您应该将此作为错误报告给 QC。

      【讨论】:

      • 是的,我有点想知道自己... ;)
      • 不是我,但我想这是因为如果它是设计使然,那么它就不是错误。正如类助手实现与 Delphi.NET 等效项相比最初是“不完整的”。有些人认为这是一个错误,但 CodeGear 的官方回应是“设计使然”——类助手不支持它们不打算用于的事情这一事实不是“错误”。要求他们做比他们目前设计的更多的事情是一个增强请求。 :)
      猜你喜欢
      • 1970-01-01
      • 2015-01-27
      • 1970-01-01
      • 2017-04-12
      • 2019-05-30
      • 1970-01-01
      • 1970-01-01
      • 2011-03-02
      • 2022-01-23
      相关资源
      最近更新 更多