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