Delphi 泛型类型(XE4+)中的构造函数约束出现问题

Delphi 泛型类型(XE4+)中的构造函数约束出现问题,delphi,generics,constructor,constraints,Delphi,Generics,Constructor,Constraints,我有一个具有一些基本功能的泛型类,所有这些都很好地工作,直到有一刻,我想跳过为简单工厂分配ConstructMethod,它只使用.Create without参数或任何细节来构造对象: type EGenericFactory = class(Exception) public constructor Create; reintroduce; end; EGenericFactoryNotRegistered = class(EGenericFactory); E

我有一个具有一些基本功能的泛型类,所有这些都很好地工作,直到有一刻,我想跳过为简单工厂分配ConstructMethod,它只使用.Create without参数或任何细节来构造对象:

type
  EGenericFactory = class(Exception)
  public
    constructor Create; reintroduce;
  end;

  EGenericFactoryNotRegistered = class(EGenericFactory);
  EGenericFactoryAlreadyRegistered = class(EGenericFactory);

  TGenericFactoryConstructor<C: constructor; R: class> = reference to function(AClass: C; AParams: array of const): R;

  TGenericFactory<T; C: constructor; R: class> = class
  protected
    FType2Class: TDictionary<T, C>;
    FConstructMethod: TGenericFactoryConstructor<C, R>;
    procedure SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
  public
    constructor Create(AConstructor: TGenericFactoryConstructor<C, R> = nil); reintroduce; overload; virtual;
    destructor Destroy; override;

    procedure RegisterClass(AType: T; AClass: C);
    function ClassForType(AType: T): C;
    function TypeForClass(AClass: TClass): T;
    function SupportsClass(AClass: TClass): Boolean;
    function Construct(AType: T; AParams: array of const): R;
    property ConstructMethod: TGenericFactoryConstructor<C, R> read FConstructMethod write SetConstructMethod;
  end;

工厂模式与GoF书中的许多其他模式一样,是一种解决语言中缺少的特性的方法,在这种情况下,Java中缺少虚拟构造函数

在Delphi中通常这样做:

使用虚拟构造函数创建祖先类可以有参数,也可以没有参数。 派生重写此构造函数的子体。 创建一个TAncestor TMetaclass类。 使用元类创建子体的实例。 完成了

例如:

type 
  TParent = class(TObject)
  public
    constructor Create; virtual;  //virtual-> system resolves the actual type at runtime
  end;

  TParentClass = class of TParent; //Meta class

  TChildA = class(TParent)
  public
    constructor Create; override; //Don't forget to call inherited in the body.      
  end;

  TChildB ....

  implementation

  var
    Input: TArray<TParentClass>;
    Output: TArray<TParent>;

  procedure CreateLotsOfObjects(const input: TArray<TParentClass>): TArray<TParent>;
  var
    X: TParentClass;
    i: integer;
  begin
    SetLength(Result, Length(input));
    i:= 0;
    for X in input do begin
      //because the constructor is virtual it will select the right one.
      //no need for a factory pattern or reflection.
      Result[i]:= X.Create; 
      Inc(i); 
    end;
  end;  

  procedure Test;
  begin
    SetLength(input,200);
    for i:= 0 to 199 do begin
      if Odd(i) then Input[i]:= TChildA else Input[i]:= TChildB;
    end; 
    Output:= CreateLotsOfObjects(input); //Creates 100 A's and 100 B's
  end;

我仍然无法指示编译器将ClassForType函数的结果理解为类外引用的类,而不是类实例,但我找到了至少调用默认构造函数的方法:

function TGenericFactory<T, C, R>.Construct(AType: T; AParams: array of const): R;
var
  ClsRaw: C;
  Cls: TClass;
begin
  if not Assigned(FConstructMethod) then
    begin
      ClsRaw := ClassForType(AType);
      Move(ClsRaw, Cls, SizeOf(C));
      Exit(R(Cls.Create));
    end;

  Result := FConstructMethod(ClassForType(AType), AParams);
end;

所有的神奇之处在于,我们只需将ClassForType的结果保存到局部变量,然后将内存复制到TClass类型的变量。然后我们可以正常调用Create

这里的问题是,您需要显式引用实际类,以便工厂创建它们。在这种情况下,德尔福没有必要首先建立工厂。只需使用虚拟构造函数就可以了。您正在将Java代码/概念移植到Delphi。Java在这里需要反射,因为它没有虚拟构造函数,Delphi不需要这种开销,实际上它不需要移植。我只是想避免每次都定义construct方法。而且我也不想一直从TClass铸造。这个工厂为我需要的每一个工厂从锅炉板中节省。我不接受结果为TObject的工厂在此之后进行强制转换。因为使用我当前的泛型工厂,我构造并将其与任何类和任何带或不带参数的虚拟构造函数一起使用。是。在这里,您的工厂具有精确的常量元类类型!但是我的可以接受任何代码,我不会像你一样复制任何代码。一旦你得到TOtherBase的TOnetherBaseClass=类-你也将复制粘贴这个元类的代码。你向我展示的是只为一个特定TParentClass实现的类似工厂的东西,而不是像我这样的通用工厂。。。一旦通过了确切的类,就得到了依赖关系。在我的例子中,我只知道基类,但通过某种枚举类型引用它们。每个特定类在工厂中注册,以将值映射到该类。然后你甚至对其他类一无所知,你只依赖于工厂,工厂只依赖于基础元类。我希望您能理解……这样就不会依赖于某些类了。它们只是包含在项目中或被排除在外,这就是您对我的案例的所有依赖关系。那么您知道如何获得ClassForTypeAType:T:C结果的TypeInfo或GetType吗?这将非常有帮助,Johan。作为参考,你可以查看Head-First设计模式-关于工厂模式。并且对于使用Factory构造的类没有依赖关系。
F := TViewFactory.Create;
F.ConstructMethod :=
  function(AClass: TConfigViewClass; AParams: array of const): TConfigView
  begin
    if AClass = nil then
      Result := nil
    else
      Result := AClass.Create;
  end;
type 
  TParent = class(TObject)
  public
    constructor Create; virtual;  //virtual-> system resolves the actual type at runtime
  end;

  TParentClass = class of TParent; //Meta class

  TChildA = class(TParent)
  public
    constructor Create; override; //Don't forget to call inherited in the body.      
  end;

  TChildB ....

  implementation

  var
    Input: TArray<TParentClass>;
    Output: TArray<TParent>;

  procedure CreateLotsOfObjects(const input: TArray<TParentClass>): TArray<TParent>;
  var
    X: TParentClass;
    i: integer;
  begin
    SetLength(Result, Length(input));
    i:= 0;
    for X in input do begin
      //because the constructor is virtual it will select the right one.
      //no need for a factory pattern or reflection.
      Result[i]:= X.Create; 
      Inc(i); 
    end;
  end;  

  procedure Test;
  begin
    SetLength(input,200);
    for i:= 0 to 199 do begin
      if Odd(i) then Input[i]:= TChildA else Input[i]:= TChildB;
    end; 
    Output:= CreateLotsOfObjects(input); //Creates 100 A's and 100 B's
  end;
function TGenericFactory<T, C, R>.Construct(AType: T; AParams: array of const): R;
var
  ClsRaw: C;
  Cls: TClass;
begin
  if not Assigned(FConstructMethod) then
    begin
      ClsRaw := ClassForType(AType);
      Move(ClsRaw, Cls, SizeOf(C));
      Exit(R(Cls.Create));
    end;

  Result := FConstructMethod(ClassForType(AType), AParams);
end;