通用工厂

时间:2009-07-09 12:40:49

标签: delphi generics delphi-2009 factory

假设我有一个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);

6 个答案:

答案 0 :(得分:6)

好吧,你可以写

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

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

修改

要回答关于如何在不需要更改工厂的情况下添加新类的注释 - 我将为您提供一些适用于非常旧的Delphi版本的简单示例代码,Delphi 2009应该采用更好的方法来实现此目的。< / p>

每个新的后代类只需要在工厂注册。可以使用多个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;

答案 1 :(得分:5)

Result := Model.Create;

也应该有用。

答案 2 :(得分: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;

答案 3 :(得分:4)

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

答案 4 :(得分:2)

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

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

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

  // 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');

答案 5 :(得分: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.