2009-07-09 13 views
2

nehme an, ich habe einen TModel:Allgemein Fabrik

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

und 2 Nachkommen:

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

und eine Fabrik:

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

Jetzt möchte ich ein wenig Refactoring:

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; 

Bisher ist es in Ordnung, aber jedes Mal, wenn ich einen TModel Nachkommen erzeuge, muss ich die Fabrik case Anweisung ändern.

Meine Frage: Ist das möglich, eine 100% generische Fabrik für alle meine TModel Nachkommen zu schaffen, so dass jedes Mal, wenn ich schaffen ein TModel Nachkommen Ich muss TModelFactory nicht ändern?

Ich habe versucht, mit Delphi 2009 Generika zu spielen, fand aber keine wertvollen Informationen, alle sind im Zusammenhang mit der grundlegenden Verwendung von TList<T> und so weiter.

aktualisiert Sorry, aber vielleicht bin ich nicht klar oder nicht verstehe Ihre Antwort (Ich bin immer noch ein noob), aber was ich versuche zu erreichen:

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

Antwort

4

rufen Sie einfach Wenn ich richtig verstehe Ihre Frage, schrieb ich etwas ähnliches hier http://www.malcolmgroves.com/blog/?p=331

+0

Hallo Malcolm, danke für Ihre Antwort.Ich habe versucht, Ihre sehr elegante Lösung zu implementieren, aber ich stieß auf ein Speicherleck. Ich habe einen Kommentar auf Ihrem Blog Beitrag – Fred

+0

Vielen Dank Fred, Ja, es war ein Fehler im TestGetInstance Test, nicht in der Fabrik selbst. Ich habe den Download repariert, also solltest du jetzt gut sein. –

+0

Danke Malcolm für dein Update. – Fred

5
Result := Model.Create; 

sollte auch funktionieren.

+1

Ja, das ist der einfachste Weg. Benötigt möglicherweise einen virtuellen Konstruktor auf der Basis (aber nur, wenn die Nachkommen einen eigenen Konstruktorcode haben). –

6

Nun, könnten Sie

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

schreiben, aber dann müssen Sie nicht eine Fabrik mehr benötigen. Normalerweise hätte man einen Selektor eines anderen Typs, wie eine Ganzzahl oder eine String-ID, um die konkrete Klasse auszuwählen, die die Fabrik erzeugen soll.

Edit:

Ihre Anmerkung an zu beantworten, wie ohne die Notwendigkeit, neue Klassen hinzuzufügen, um die Fabrik zu ändern - ich gibt Ihnen ein paar einfachen Beispielcode, der für sehr alte Delphi-Versionen funktioniert, Delphi 2009 sollte Upen viel bessere Möglichkeiten, dies zu tun.

Jede neue Nachkommenklasse muss nur im Werk registriert werden. Die gleiche Klasse kann mit mehreren IDs registriert werden. Der Code verwendet eine Zeichenfolge-ID, aber Ganzzahlen oder GUIDs funktionieren genauso gut.

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

Ein Tippfehler? Du meintest wahrscheinlich Model.Create. –

+0

Ja in der Tat, vielen Dank für das Erkennen. – mghie

+0

Danke für deine Antwort: "um die konkrete Klasse zu wählen, die die Fabrik erstellen soll" Genau das möchte ich tun, wie geht das? (Ohne die Fabrik für jeden neuen Nachfolger ändern zu müssen) – Fred

5

Die Lösung mit Model.Create funktioniert, wenn der Konstruktor virtuell ist.

Wenn Sie delphi 2009 verwenden, können Sie einen anderen Trick mit Generika verwenden:

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; 
2

Es gibt wohl einen einfacheren Weg, dies zu erreichen. Ich glaube mich daran zu erinnern, das eingebaute TClassList-Objekt gefunden zu haben, mit dem das gehandhabt wurde, aber an diesem Punkt funktionierte das bereits. TClassList hat keine Möglichkeit, die gespeicherten Objekte anhand des String-Namens nachzuschlagen, aber es könnte immer noch nützlich sein.

Um dies zu ermöglichen, müssen Sie Ihre Klassen mit einem globalen Objekt registrieren. Auf diese Weise kann eine String-Eingabe für den Klassennamen vorgenommen werden. Suchen Sie diesen Namen in einer Liste, um das richtige Klassenobjekt zu finden.

In meinem Fall habe ich eine TStringList verwendet, um die registrierten Klassen zu halten, und ich verwende den Klassennamen als Bezeichner für die Klasse. Um die Klasse dem Objekt "object" der String-Liste hinzuzufügen, musste ich die Klasse in ein reales Objekt umbrechen. Ich gebe zu, dass ich die "Klasse" nicht wirklich verstehe, also ist das vielleicht nicht nötig, wenn Sie alles richtig machen.

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

Ich habe ein globales "PluginManager" -Objekt. Hier werden Klassen registriert und erstellt. Die "AddClass" -Methode setzt die Klasse in die TStringList, damit ich sie später nachschlagen kann.

In jeder Klasse, die ich erstelle, füge ich es der Klassenliste im Abschnitt "Initialisierung" hinzu.


initialization; 
    AgPluginManager.AddClass(TMyPluginObject); 

Dann, wenn es darum geht, die Klasse erstellen ich den Namen in der Stringliste nachschlagen kann, die Klasse finden und es schaffen. In meiner eigentlichen Funktion überprüfe ich, ob der Eintrag vorhanden ist und mit Fehlern usw. umgeht. Ich gebe auch mehr Daten an den Klassenkonstruktor weiter. In meinem Fall erstelle ich Formulare, so dass ich das Objekt nicht wirklich an den Aufrufer zurücksende (ich verfolge sie in meinem PluginManager), aber das wäre bei Bedarf einfach.

Seit ich dies zum ersten Mal schrieb, brauchte ich den Code nicht zu berühren. Ich stelle nur sicher, dass ich meine neuen Klassen der Liste in ihrem Initialisierungsabschnitt hinzufüge und alles funktioniert.

ein Objekt zu erstellen, ich


    PluginManger.Execute('TMyPluginObject'); 
1

Sie generische Fabrik wie dies tun: Aber das einzige Problem, sollten Sie setzen das generische Konstrukt Verfahren für jede der Fabrik letzte Klasse wie folgt vor:

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; 

und die Einheit für die Fabrik ist:

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.