2

假设我有一个 TModel:

TModelClass = class of TModel;
TModel = class
  procedure DoSomeStuff;
end;

和2个后代:

TModel_A = class(TModel);
TModel_B = class(TModel);

和工厂:

TModelFactory = class
  class function CreateModel_A: TModel_A;
  class function CreateModel_B: TModel_B;
end;

现在我想重构一下:

TModelFactory = class
  class function CreateGenericModel(Model: TModelClass) : TModel
end;

class function TModelFactory.CreateGenericModel(Model: TModelClass) : TModel
begin
  ...
  case Model of
    TModel_A: Result := TModel_A.Create;
    TModel_B: Result := TModel_B.Create;
  end;
  ...
end;

到目前为止还可以,但是每次创建TModel后代时,我都必须修改工厂case语句。

我的问题:这是否可以为我所有的TModel后代创建一个 100% 的通用工厂,所以每次创建TModel后代时我都不必修改TModelFactory

我尝试使用Delphi 2009泛型但没有找到有价值的信息,都与基本用法等TList<T>有关。

更新 对不起,但也许我不清楚或不明白你的答案(我仍然是菜鸟),但我想要实现的是:

var
  M: TModel_A;
begin
  M: TModelFactory.CreateGenericModel(MY_CONCRETE_CLASS);
4

6 回答 6

6

嗯,你可以写

class function TModelFactory.CreateGenericModel(AModelClass: TModelClass): TModel;
begin
  Result := AModelClass.Create;
end;

但是你不再需要工厂了。通常会有一个不同类型的选择器,比如整数或字符串 ID,来选择工厂应该创建的具体类。

编辑:

为了回答您关于如何在不需要更改工厂的情况下添加新类的评论 - 我将为您提供一些适用于非常旧的 Delphi 版本的简单示例代码,Delphi 2009 应该会提供更好的方法来做到这一点。

每个新的后代类只需要在工厂注册。可以使用多个 ID 注册同一类。该代码使用字符串 ID,但整数或 GUID 也可以正常工作。

type
  TModelFactory = class
  public
    class function CreateModelFromID(const AID: string): TModel;
    class function FindModelClassForId(const AID: string): TModelClass;
    class function GetModelClassID(AModelClass: TModelClass): string;
    class procedure RegisterModelClass(const AID: string;
      AModelClass: TModelClass);
  end;

{ TModelFactory }

type
  TModelClassRegistration = record
    ID: string;
    ModelClass: TModelClass;
  end;

var
  RegisteredModelClasses: array of TModelClassRegistration;

class function TModelFactory.CreateModelFromID(const AID: string): TModel;
var
  ModelClass: TModelClass;
begin
  ModelClass :=  FindModelClassForId(AID);
  if ModelClass <> nil then
    Result := ModelClass.Create
  else
    Result := nil;
end;

class function TModelFactory.FindModelClassForId(
  const AID: string): TModelClass;
var
  i, Len: integer;
begin
  Result := nil;
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ID = AID then begin
      Result := RegisteredModelClasses[i].ModelClass;
      break;
    end;
end;

class function TModelFactory.GetModelClassID(AModelClass: TModelClass): string;
var
  i, Len: integer;
begin
  Result := '';
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ModelClass = AModelClass then begin
      Result := RegisteredModelClasses[i].ID;
      break;
    end;
end;

class procedure TModelFactory.RegisterModelClass(const AID: string;
  AModelClass: TModelClass);
var
  i, Len: integer;
begin
  Assert(AModelClass <> nil);
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if (RegisteredModelClasses[i].ID = AID)
      and (RegisteredModelClasses[i].ModelClass = AModelClass)
    then begin
      Assert(FALSE);
      exit;
    end;
  SetLength(RegisteredModelClasses, Len + 1);
  RegisteredModelClasses[Len].ID := AID;
  RegisteredModelClasses[Len].ModelClass := AModelClass;
end;
于 2009-07-09T12:48:46.227 回答
5
Result := Model.Create;

也应该工作。

于 2009-07-09T12:46:13.700 回答
5

如果构造函数是虚拟的,则使用 Model.Create 的解决方案有效。

如果您使用 delphi 2009,您可以使用另一个使用泛型的技巧:

type 
  TMyContainer<T: TModel, constructor> (...)
  protected
    function CreateModel: TModel;
  end;

function TMyContainer<T>.CreateModel: TModel;
begin
  Result := T.Create; // Works only with a constructor constraint.   
end;
于 2009-07-09T13:11:52.070 回答
4

如果我正确理解你的问题,我在这里写了类似的东西http://www.malcolmgroves.com/blog/?p=331

于 2009-07-10T00:27:17.770 回答
2

可能有一种更简单的方法可以实现这一点。我似乎记得找到了处理这个的内置 TClassList 对象,但是这一点我已经有了这个工作。TClassList 没有办法通过字符串名称查找存储的对象,但它仍然很有用。

基本上要完成这项工作,您需要使用全局对象注册您的类。这样,它可以为类名输入字符串,在列表中查找该名称以找到正确的类对象。

在我的例子中,我使用 TStringList 来保存注册的类,并使用类名作为类的标识符。为了将类添加到字符串列表的“对象”成员中,我需要将类包装在一个真实的对象中。我承认我并不真正理解“类”,所以如果你把所有东西都投对了,这可能就不需要了。

  // Needed to put "Class" in the Object member of the
  // TStringList class
  TClassWrapper = class(TObject)
  private
    FGuiPluginClass: TAgCustomPluginClass;
  public
    property GuiPluginClass: TAgCustomPluginClass read FGuiPluginClass;
    constructor Create(GuiPluginClass: TAgCustomPluginClass);
  end;

我有一个全局“PluginManager”对象。这是注册和创建类的地方。“AddClass”方法将类放在 TStringList 中,以便稍后查找。


procedure TAgPluginManager.AddClass(GuiPluginClass: TAgCustomPluginClass);
begin
  FClassList.AddObject(GuiPluginClass.ClassName,
    TClassWrapper.Create(GuiPluginClass));
end;

在我创建的每个类中,我将其添加到“初始化”部分的类列表中。


initialization;
  AgPluginManager.AddClass(TMyPluginObject);

然后,当需要创建类时,我可以在字符串列表中查找名称,找到类并创建它。在我的实际函数中,我正在检查以确保条目存在并处理错误等。我还将更多数据传递给类构造函数。就我而言,我正在创建表单,因此我实际上并没有将对象返回给调用者(我在我的 PluginManager 中跟踪它们),但如果需要,这很容易做到。


procedure TAgPluginManager.Execute(PluginName: string);
var
  ClassIndex: integer;
  NewPluginWrapper: TClassWrapper;
begin
    ClassIndex := FClassList.IndexOf(PluginName);
    if ClassIndex > -1 then
    begin
      NewPluginWrapper := TClassWrapper(FClassList.Objects[ClassIndex]);
      FActivePlugin := NewPluginWrapper.GuiPluginClass.Create();
    end;
end;

自从我第一次写这篇文章以来,我不需要接触代码。我只是确保将我的新类添加到其初始化部分的列表中,一切正常。

要创建一个我只是调用的对象


  PluginManger.Execute('TMyPluginObject');
于 2009-07-09T15:31:34.400 回答
1

你可以像这样做通用工厂:但是你应该为每个工厂最终类设置通用构造方法的唯一问题是这样的:

type
  TViewFactory = TGenericFactory<Integer, TMyObjectClass, TMyObject>;
...
F := TViewFactory.Create;
F.ConstructMethod :=
  function(AClass: TMyObjectClass; AParams: array of const): TMyObject
  begin
    if AClass = nil then
      Result := nil
    else
      Result := AClass.Create;
  end;

工厂的单位是:

unit uGenericFactory;

interface

uses
  System.SysUtils, System.Generics.Collections;

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;

implementation

uses
  System.Rtti;

{ TGenericFactory<T, C, R> }

function TGenericFactory<T, C, R>.ClassForType(AType: T): C;
begin
  FType2Class.TryGetValue(AType, Result);
end;

function TGenericFactory<T, C, R>.Construct(AType: T; AParams: array of const): R;
begin
  if not Assigned(FConstructMethod) then
    Exit(nil);

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

constructor TGenericFactory<T, C, R>.Create(AConstructor: TGenericFactoryConstructor<C, R> = nil);
begin
  inherited Create;
  FType2Class := TDictionary<T, C>.Create;
  FConstructMethod := AConstructor;
end;

destructor TGenericFactory<T, C, R>.Destroy;
begin
  FType2Class.Free;
  inherited;
end;

procedure TGenericFactory<T, C, R>.RegisterClass(AType: T; AClass: C);
begin
  if FType2Class.ContainsKey(AType) then
    raise EGenericFactoryAlreadyRegistered.Create;
  FType2Class.Add(AType, AClass);
end;

procedure TGenericFactory<T, C, R>.SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
begin
  FConstructMethod := Value;
end;

function TGenericFactory<T, C, R>.SupportsClass(AClass: TClass): Boolean;
var
  Key: T;
  Val: C;
begin
  for Key in FType2Class.Keys do
    begin
      Val := FType2Class[Key];
      if CompareMem(@Val, AClass, SizeOf(Pointer)) then
        Exit(True);
    end;

  Result := False;
end;

function TGenericFactory<T, C, R>.TypeForClass(AClass: TClass): T;
var
  Key: T;
  Val: TValue;
begin
  for Key in FType2Class.Keys do
    begin
      Val := TValue.From<C>(FType2Class[Key]);
      if Val.AsClass = AClass then
        Exit(Key);
    end;

  raise EGenericFactoryNotRegistered.Create;
end;

{ EGenericFactory }

constructor EGenericFactory.Create;
begin
  inherited Create(Self.ClassName);
end;

end.
于 2014-08-20T07:17:47.770 回答