Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Custom Constructor /DI bei factory-basierter Objekterstellung (https://www.delphipraxis.net/195026-custom-constructor-di-bei-factory-basierter-objekterstellung.html)

Sequitar 26. Jan 2018 23:38

Custom Constructor /DI bei factory-basierter Objekterstellung
 
hallo, zusammen.
ich arbeite zzt relativ viel über DLL grenzen und hab das ganze auch schon gut mit interfaces und einer zentralen factory zur Registrierung und Erstellung neuer Typen hinbekommen.

Jetzt würde ich neben der bisherigen Vorgehensweise (Interfaces, Value access per properties )auch gerne objekte bereits bei der Erstellung mit startparametern ausstatten (incl. und besonders auch i.V.m Dependency injection).

Die Frage die sich mir stellt, ist wie ich hierzu eigens definierte / verdeckende constructoren einführe, die ich in einer Factory nutzen kann, was ja bei normaler Vererbung möglich ist (also z.b.
Delphi-Quellcode:
// in einer DLL registrierter Typ
Type
  Ix = Interface
    [GUID]
    Function Getint: Integer;
    Procedure Setint(Anint: Integer);
    Property Anint: Integer Read Getint Write Setint;
  End;

  Tx = Class(Tinterfacedobject, Ix)
  Private
    Fint: Integer;
    Function Getint: Integer;
    Procedure Setint(Anint: Integer);
  Public
    Constructor Create(Anint: Integer);
  End;

  { ... }
Constructor Tx.Create(Anint: Integer);
Begin
  Fint := Anint;
End;

Function Tx.Getint: Integer;
Begin
  Result := Fint;
End;

Procedure Tx.Setint(Anint: Integer);
Begin
  Fint := Anint;
End;

Initialization

// globale factory, oder davon abgeleitet: Txfactory=Tfactory<TX>;
Txfactory.Register(Tx); // läuft soweit

Finalization

Txfactory.Unregister(Tx);
in anderer unit /anderem DLL modul soll dann sowas aufgerufen werden
Delphi-Quellcode:
Procedure New;
Var
  Xclass: Txclass;
  // txclass ist der hauptanwendung und anderen modulen ja an sich unbekannt, daher später vlt
  // Var xclass:tinterfacedclass;
  X,y: Tinterfacedobject;
Begin
  Xclass := Txclass { Tinterfacedclass } (Txfactory.Getclass('tx'));//txclass i.A. unbekannt, da in externer DLL definiert und registriert. steht jetz nur mal so da zur verdeutlichung.
  // custom constructor, future dependency injection planned
  X := Xclass.Create(5);
//oder auch möglich
y:=txfactory.new('tx');
  // << << Wie Kann Ich in beiden fällen Auf Den Custom Constructor Von Tx Zugreifen ? TX und TXclass sind normal ausserhalb der DDL unbekannt
  Assert((X as ix).Anint = 5);
  X.Free;
  y.free;
End;
bisher mach ich das dann so, kann aber keine dependencies mitgeben //fehlende konstructoren:
Delphi-Quellcode:
procedure new;
var x,y:ix;
xclass:tinterfacedclass;
 Xclass := Tinterfacedclass{Txclass}(Txfactory.Getclass('tx'));
        // custom constructor, future dependency injection planned
 X := Xclass.create as ix;//<<<< wie kann ich hier auf den custom constructor von tx zugreifen?
x.anint:=5;
 Assert(X.Anint = 5);
x:=nil;
//oder so:
y:=txfactory.new('tx')as ix;
y.anint:=42;
y:=nil;
{...}
ich könnte mir auch vorstellen, die jeweiligen constructoren direkt mit in der factory zu registrieren (reference to constructor gibts aber leider nicht, theoretisch würde das mein problem ja lösen, aber ich weiss einfach nicht wie das zu realisieren ist)
also sowas in der art:
Delphi-Quellcode:
txfactory.register(tx,tx.create({custom parameters}));

freimatz 29. Jan 2018 07:44

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Leider kann ich dir nicht direkt weiterhelfen. Was ich sagen kann ist, dass es geht auf die Parameter der Konstruktoren einer Klasse zuzugreifen. Unser selbstgeschriebener Dependecy-Container kann das zumindest. Der Code der den Contrainer verwendet benutzt jedoch nie den Konstruktor für andere Parameter. Der Konstruktor wird nur verwendet um Abhängigkeiten zu injizieren. Weitere Werte kommen dann auch per Methoden oder Eigenschaften rein.
(Es gab hier mal eine Diskussion den Dependecy-Container zu erweitern, so dass man auch die berüchtigen "AOwner" Parameter übergeben kann. Ich gehe aber davon aus, dass der Dependecy-Container dann eine weitere Methode bekommen hätte.)

Sequitar 29. Jan 2018 09:58

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von freimatz (Beitrag 1392347)
Leider kann ich dir nicht direkt weiterhelfen. Was ich sagen kann ist, dass es geht auf die Parameter der Konstruktoren einer Klasse zuzugreifen. Unser selbstgeschriebener Dependecy-Container kann das zumindest. Der Code der den Contrainer verwendet benutzt jedoch nie den Konstruktor für andere Parameter. Der Konstruktor wird nur verwendet um Abhängigkeiten zu injizieren. Weitere Werte kommen dann auch per Methoden oder Eigenschaften rein.
(Es gab hier mal eine Diskussion den Dependecy-Container zu erweitern, so dass man auch die berüchtigen "AOwner" Parameter übergeben kann. Ich gehe aber davon aus, dass der Dependecy-Container dann eine weitere Methode bekommen hätte.)


Okay danke für die Antwort schonmal. es ging mir ja auch hauptsächlich um die von dir genannten abhängingkeiten
Wie habt ihr denn diese Konstruktoren aufgerufen (gemeinsame baseclass?), in welcher form waren die angängigkeiten? ich natürlich kann ich werte per eigenschaften über diverse interfaces setzen , aber das Problem (bei euch gings ja irgendwie) was ich hab, ist dass ich durch die factory ja nicht
Delphi-Quellcode:
Tx.Create(Anint: Integer)
aufrufen kann, ausser wenn ich ne basisklasse bereits kenne die den konstruktor zumindest abstrakt kennt.

Das mit den interfaces ist ein eigentlich praktikabler ansatz, den ich auch bisher verfolge, nur kann ich bei Objekterstellung halt den nutzer nicht ZWINGEN, möglicherweise notwendige initiale abhängigkeiten zu übergeben, bevor ers vlt vergisst als property zuzuweisen..., ausserdem bräuchte ich dann bei jedem "receiver" einer injektion dann sowas wie "Finjectable.init", also müsste ich dann ein extra interface erstellen, das von allen möglichen dependencies zu implementieren wäre...geht zwar, find ich aber irgenwie unpraktisch und unschön dazu.


Zitat:

Was ich sagen kann ist, dass es geht auf die Parameter der Konstruktoren einer Klasse zuzugreifen
...Ist das nicht das, was ich gerade denke nich zu können?^^
Wie ist das gemeint? b) wie wäre das denn möglich? Via RTTI, vielleicht, damit hab ich bisher noch fast gar keuine Erfahrung.Merci

freimatz 29. Jan 2018 15:06

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Ja RTTI - und auch ich habe da fast keine Erfahrung damit.

Sequitar 7. Feb 2018 10:34

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Hab das jetzt so zu lösen versucht. Nicht elegant und definitiv ausbaufähig(je nachdem wo man als nächstes gegen eine wand läuft:shock:) //bisher fiel mir keine bessere Lösung ein für das problem, als ein neues interface einzuführen auf das bei erstellung geprüft wird und bei erfolg die enstprechenden abhängigkeiten mitgegeben werden. Man KÖNNTE das dann wohl auch über delegates den einzelen objekten mitgeben, damit die nicht so "hässlich" werden. Die frage bleibt ob das nicht auch einfacher / eleganter geht??
Delphi-Quellcode:
Type
//Nachteil? IInjectable müsste dann von jedem "abhängigen" object implementiert werden (zb auch über delegates)

  Iinjectable = Interface
    ['{ECC8E26C-7D14-43FD-9247-066435DAEDA4}']
    Procedure Inject(Dependencies: Array Of Iinterface);
    // Function DependsOn(Dependency: Tobject): Boolean;
  End;

  Tinjectfactory = Class(Tifactory) Class
  Function New(Classname: String; Dependencies: Array Of IInterface)
    : Iinterface;overload; Static;
  End;
//[...]
Class Function Tinjectfactory.New(Classname: String;
  Dependencies: Array Of IInterface): Iinterface;
Var
  Temp: Iinjectable;
Begin
  Result := {inherited} New(Classname);//new(param:string)inherited from parent class
  Try
    If Supports(Result, Iinjectable, Temp)
    Then
      Temp.Inject(Dependencies);
  Except
    Raise
  End;
End;

//Nutzungsbeispiel

Procedure Tinjecttest.Test_factoryinject;
Var
  Parent: Iinterface;
  Bla: Ibla;
Begin
  Try
    Tinjectfactory.Register([Tinjectabletestobject, Tbla, Tblub]);
    Parent := Tinjectfactory.New('Tinjecttest.Tinjectabletestobject',
      [Tbla.Create, Tblub.Create]); //statt direkterstellung auch zb verschachtelung mehrerer "abhängiger" factoryaufrufe
    // assert(iinjectable(parent).dependson())
    (Parent As Ibla).Bla;
    (Parent As Iblub).Blub;
    Bla := Parent As Ibla;
    Bla.Bla;
    Tinjectfactory.UnRegister([Tinjectabletestobject, Tbla, Tblub]);
  Except
    On E: Exception Do
      Raise E
  End;
End;

Stevie 7. Feb 2018 17:18

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Du könntest den DI Container von Spring4D einsetzen ;)
Damit kannst du alles injecten, was du willst.

Aber dessen ungeachtet meine Empfehlung: designe den code so, dass du ihn über "pure DI" (sprich, wie würde ich das per hand machen) nutzen kannst - der Einsatz eines wie auch immer gearteten Containers kommt dann bloß oben drauf. Also keine versteckten extra für DI Konstruktoren oder Interfaces.

Sequitar 7. Feb 2018 21:55

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von Stevie (Beitrag 1393317)
Du könntest den DI Container von Spring4D einsetzen ;)
Damit kannst du alles injecten, was du willst.

Aber dessen ungeachtet meine Empfehlung: designe den code so, dass du ihn über "pure DI" (sprich, wie würde ich das per hand machen) nutzen kannst - der Einsatz eines wie auch immer gearteten Containers kommt dann bloß oben drauf. Also keine versteckten extra für DI Konstruktoren oder Interfaces.

Nachdem was ich hier lese, ist S4D normalerweise perfekt, ich denke nur, dass es den Rahmen sprengt...Zumal man sich da komplett reindenken muss. Bisher hab ich mir halt das meiste selbst angeeignet, oder durch die super betreuung hier im forum^^ :P
Natürlich wird sich sowas bei Großprojekten lohnen, bei mir handelt es sich eher um relativ kleine Anwendungen. However, würde ich auch dort gerne möglichst flexibel bleiben und einen einheitlichen Stil etablieren....

Gut, die Interfaces werd ich wohl nutzen müssen, da ich über dynamisch ladende DLLs arbeite. Daher lass ich mir alle objekte über eine zentrale stelle registieren und kann dann über entsprechende factories, die schnittstellen (intf.) zurückliefern. Das läuft ja alles soweit. Nur verstecken wollte ich eigentlich nichts (ausser wie üblich Encapsulation), mir ist nur noch kein weg eingefallen (normalerweise könnte man das, zb bei statischen packages, mit metaclasses (class of) lösen, aber da ich in den DLLS nicht an die klassen komme, wird das nur bei gemeinsamen vorfahren gehen und lässt sich somit auch relativ schwer verallgemeinern...?

Wie genau ist das mit dem "per Hand machen" (pure DI) gemeint? Ist das nicht sowas wie
Delphi-Quellcode:
Constructor TX.Create(dep:TY;dep2:TZ);
begin
//init with dep,dep2
end;

freimatz 8. Feb 2018 09:25

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von Sequitar (Beitrag 1393331)
Nachdem was ich hier lese, ist S4D normalerweise perfekt, ich denke nur, dass es den Rahmen sprengt...Zumal man sich da komplett reindenken muss. Bisher hab ich mir halt das meiste selbst angeeignet, oder durch die super betreuung hier im forum^^ :P
Natürlich wird sich sowas bei Großprojekten lohnen, bei mir handelt es sich eher um relativ kleine Anwendungen. However, würde ich auch dort gerne möglichst flexibel bleiben und einen einheitlichen Stil etablieren....

1. Also wenn du dich schon mit "Custom Constructor /DI bei factory-basierter Objekterstellung" beschäftigst, dann lohnt sich das Reindenken auf jeden Fall. Oder umgekehrt kannst auch gleich das Thema lassen.
2. Der Unterschied ist nicht zwischen groß und klein, sondern eher Hobby oder beruflich. Wenn Du professionell Softwareentwickler bist, dann musst du m.E. dich eh in das Thema reindenken.

Stevie 8. Feb 2018 09:35

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von Sequitar (Beitrag 1393331)
Wie genau ist das mit dem "per Hand machen" (pure DI) gemeint? Ist das nicht sowas wie
Delphi-Quellcode:
Constructor TX.Create(dep:TY;dep2:TZ);
begin
//init with dep,dep2
end;

Ja, mit dem kleinen unterschied, dass der Konstruktor am besten auch interfaces injected bekommt und nicht Objekte.

Pure DI ist dann das hier:

Delphi-Quellcode:
var
  x: IX;
  y: IY;
  z: IZ;
begin
  y := TY.Create(...);
  z := TZ.Create(...);
  x := TX.Create(y, z);
Und genau das macht ein DI Container auch. Wenn du ein IX haben willst, weiß er, dass er erst ein IY und IZ braucht (also TY und TZ bauen muss, evtl mit deren Abhängigkeiten) und gibt die dann beim Konstruktoraufruf mit.

Ich hab vor einiger Zeit mal nen Mini DI Container gebaut, der das schon kann - ohne das ganze fancy Extra Zeugs, was der Spring Container kann.

Sequitar 12. Feb 2018 15:32

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von Stevie (Beitrag 1393361)
Zitat:

Zitat von Sequitar (Beitrag 1393331)
Wie genau ist das mit dem "per Hand machen" (pure DI) gemeint? Ist das nicht sowas wie
Delphi-Quellcode:
Constructor TX.Create(dep:TY;dep2:TZ);
begin
//init with dep,dep2
end;

Ja, mit dem kleinen unterschied, dass der Konstruktor am besten auch interfaces injected bekommt und nicht Objekte.

Pure DI ist dann das hier:

Delphi-Quellcode:
var
  x: IX;
  y: IY;
  z: IZ;
begin
  y := TY.Create(...);
  z := TZ.Create(...);
  x := TX.Create(y, z);
Und genau das macht ein DI Container auch. Wenn du ein IX haben willst, weiß er, dass er erst ein IY und IZ braucht (also TY und TZ bauen muss, evtl mit deren Abhängigkeiten) und gibt die dann beim Konstruktoraufruf mit.

Ich hab vor einiger Zeit mal nen Mini DI Container gebaut, der das schon kann - ohne das ganze fancy Extra Zeugs, was der Spring Container kann.

Danke für die Infos,
So hab ich mir das auch vorgestellt.(IY;IZ zu nutzen ist naturlich sehr sinnvoll). Das problem bei dieser Lösung ist nur dass ich bis auf ausnahmen der in einem gemeinsamen package declarierten Typen nicht so einfach anwenden kann. (Also entweder müsste ich auf statische packages zurück, alle Klassen irgendwo gemeinsame Vorfahren mit
Delphi-Quellcode:
Constructor TBase.Create(a,b,c:iirgendwas);virtual;
haben lassen oder es gibts noch einen anderen weg,über DLL's
Delphi-Quellcode:
 X:=Tx.create(y:ianinterface,z:ianotherinterface)
zu finden und mit Params Y und Z aufzurufen (evtl RTTI, so wie im o.g beispielsimple DI container)?
Und genau DAS ist zzt mein problem...ohne parameter ist ja einfach>>Tclass

Stevie 12. Feb 2018 16:24

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Die Interfaces sollten in einem Package sein (allein aus dem Grund, damit alle beteiligten Module, dieselbe Typeinfo nutzen, sonst funktioniert das mit dem Container per RTTI nicht).

Und Parameter finden musst du selber dann gar nicht. Du sagst dem Container, es gibt X, Y und Z und dann weiß er, was er zusammen tackern muss, wenn du ein IX brauchst (nämlich nen IY und IZ, die er auch allein baut).
Für weitere Informationen einfach mal Dependency Injection in Delphi lesen :)

Sequitar 12. Feb 2018 22:13

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zitat:

Zitat von Stevie (Beitrag 1393664)
Die Interfaces sollten in einem Package sein (allein aus dem Grund, damit alle beteiligten Module, dieselbe Typeinfo nutzen, sonst funktioniert das mit dem Container per RTTI nicht).

Und Parameter finden musst du selber dann gar nicht. Du sagst dem Container, es gibt X, Y und Z und dann weiß er, was er zusammen tackern muss, wenn du ein IX brauchst (nämlich nen IY und IZ, die er auch allein baut).
Für weitere Informationen einfach mal Dependency Injection in Delphi lesen :)

OK so hab ichs bisher gemacht, Registrierung und interfaces in ein gemeinsames package.

Nachfrage:Ich hab nur noch nicht ganz verstanden, wie ich denn den container mit iy,iz füttere (das meinte ich mit parametern) damit er n ix ausgibt.(ausser er würde den spezifischen konstruktor für die implementierende klasse kennen,was sich bei einigen wohl machen liesse,bei denen ich genau weiss ich kann ein class of ....einsetzen, weil die basisklasse schon mit im ReGpackage steckt aber nicht als OSFA). ich mein wahrscheinlich wärs am einfachsten mit setter injection nur wärs mir am liebsten ich wüsste direkt dass ich alles komplett hab wenn das objekt erstellt wird.
ich guck mir mmal das buch an, vlt find ich da ja was passendes....merci

Stevie 13. Feb 2018 07:48

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Hier mal ein kleines Beispiel:

Delphi-Quellcode:
program Simple_DI;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  SimpleContainer in 'SimpleContainer.pas';

type
  IX = interface
    ['{E2D88E1C-AE7D-491A-B456-84B134749890}']
  end;

  IY = interface
    ['{3D1256AB-CDDE-49B2-94CF-E9FA82314206}']
  end;

  IZ = interface
    ['{4F51DE5D-B65B-4776-B25B-5B2868DC34AC}']
  end;

  TX = class(TInterfacedObject, IX)
  private
    fy: IY;
    fz: IZ;
  public
    constructor Create(const y: IY; const z: IZ);
  end;

  TY = class(TInterfacedObject, IY)
    constructor Create;
  end;

  TZ = class(TInterfacedObject, IZ)
    constructor Create;
  end;

{ TX }

constructor TX.Create(const y: IY; const z: IZ);
begin
  Writeln('creating ', ClassName);
  Writeln('injected ', (y as TObject).ClassName, ' for y');
  Writeln('injected ', (z as TObject).ClassName, ' for z');

  fy := y;
  fz := z;
end;

{ TY }

constructor TY.Create;
begin
  Writeln('creating ', ClassName);
end;

{ TZ }

constructor TZ.Create;
begin
  Writeln('creating ', ClassName);
end;

var
  container: TSimpleContainer;
  x: IX;
begin
  // showing the so called register, resole, release (RRR) pattern
  // see http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/

  container := TSimpleContainer.Create;
  try
    // register
    container.Add<IX, TX>;
    container.Add<IY, TY>;
    container.Add<IZ, TZ>;

    // resolve
    x := container.Get<IX>;
  finally
    // release
    x := nil;
    container.Free;
  end;
end.

Sequitar 10. Mär 2018 22:30

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Zwischenzeitlich woanders weitergebaut, bin heute wieder drüber gestolpert.
Hab mir folgendes überlegt. Man kann doch methoden oder constructors auch über die RTTI aufrufen.
Damit hätte ich genau was ich ursprünglich wollte....
Ein Beispiel hab ich hier gefunden:

Delphi-Quellcode:
Function NewFromClassInfo(Const ATypeInfo: PTypeInfo;{Params: Array Of Tvalue}): Tobject;
  Var
    Ctx: TRttiContext;
    RType: TRttiType;
    AMethCreate: TRttiMethod;
    InstanceType: TRttiInstanceType;
    Fvalue: Rtti.Tvalue;
  Begin
    Ctx := TRttiContext.Create;
    RType := Ctx.GetType(ATypeInfo);
    For AMethCreate In RType.GetMethods Do
    Begin
{$MESSAGE 'TODO Handle constructors with params.'}
      If Assigned(AMethCreate)
      Then
        If (AMethCreate.IsConstructor) And
          (Length(AMethCreate.GetParameters) = 0)//müsste dann entfernt werden
        Then
        Begin
          InstanceType := RType.AsInstance;
          FValue := AMethCreate.Invoke(InstanceType.MetaclassType, [{params}]);
{hatte überlegt, params hier zu übergeben. das führt jedoch zu access violation
ODER zur meldung ("parameter count mismatch").
Und JA, ich übergebe (nur anscheinend) die richtige parameterzahl.


(Also wenn create(anint:integer)>>tx.create(5);
>>newfromclassinfo(tx.classinfo,[5]);
}
          Result := Fvalue.AsObject;
          Exit;
        End;
    End;
  End;
Kann man das verbessern? Obiges beheben?
Merci.
Zitat:

Zitat von Stevie (Beitrag 1393710)
Hier mal ein kleines Beispiel:

Delphi-Quellcode:
program Simple_DI;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  SimpleContainer in 'SimpleContainer.pas';

type
  IX = interface
    ['{E2D88E1C-AE7D-491A-B456-84B134749890}']
  end;

  IY = interface
    ['{3D1256AB-CDDE-49B2-94CF-E9FA82314206}']
  end;

  IZ = interface
    ['{4F51DE5D-B65B-4776-B25B-5B2868DC34AC}']
  end;

  TX = class(TInterfacedObject, IX)
  private
    fy: IY;
    fz: IZ;
  public
    constructor Create(const y: IY; const z: IZ);
  end;

  TY = class(TInterfacedObject, IY)
    constructor Create;
  end;

  TZ = class(TInterfacedObject, IZ)
    constructor Create;
  end;

{ TX }

constructor TX.Create(const y: IY; const z: IZ);
begin
  Writeln('creating ', ClassName);
  Writeln('injected ', (y as TObject).ClassName, ' for y');
  Writeln('injected ', (z as TObject).ClassName, ' for z');

  fy := y;
  fz := z;
end;

{ TY }

constructor TY.Create;
begin
  Writeln('creating ', ClassName);
end;

{ TZ }

constructor TZ.Create;
begin
  Writeln('creating ', ClassName);
end;

var
  container: TSimpleContainer;
  x: IX;
begin
  // showing the so called register, resole, release (RRR) pattern
  // see http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/

  container := TSimpleContainer.Create;
  try
    // register
    container.Add<IX, TX>;
    container.Add<IY, TY>;
    container.Add<IZ, TZ>;

    // resolve
    x := container.Get<IX>;
  finally
    // release
    x := nil;
    container.Free;
  end;
end.


Sequitar 22. Mär 2018 18:31

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Bisschen weiter, und ich habe bisher folgendes - soweit auch funktionierend - implementiert:
Delphi-Quellcode:
Type
 Tfactory = Class(Tinterfacedobject)
  Strict Private
    Class Function Getclass(Classname: String; Out Index: Tcount;
      Out Constr: Pointer): TClass; Overload; Static; //user may register / use a custom constructor to create objects>>not fully //implemented
    Class Var Fclassregister: Iclassregister;//central register for classes and constructors, concrete impl may be changed later
  Public
    Class Constructor Create; Reintroduce;
    Class Destructor Destroy; Reintroduce;
    // Class Procedure Reg(Cl: TClass; Aconstr: Pointer = Nil); Overload; Virtual;
    Class Procedure Reg(Cl: Tclass); Overload; Virtual;
    Class Procedure Reg(Classes: Array Of TClass); Overload; Static;
    Class Procedure UnReg(Cl: TClass); Overload; Virtual;
    Class Procedure UnReg(Classes: Array Of TClass); Overload; Static;
    Class Procedure Unregall; Virtual;
    Class Function New<T: Iinterface>(Classname: String;
      Parameters: Array Of Tvalue; Customconstructorname: String = 'create')
      : T; Overload; //creation of new objects via rtti's ability to call custom methods and constructors
    Class Function New<T: Iinterface>(Classname: String): T; Overload; //create via standard "create" constructor
    Class Function IsRegistered(Classname: String): Boolean; Virtual; //check if centrally registered
    Class Function Getclass(Classname: String): TClass; Overload; Static;
    Class Function GetDecendents(Classname: String;
      Childrenonly: Boolean = True): Tstringlist; Static; //get registered decendents of a given class
  End;
Um noch mehr Flexiblität zu erhalten und später ggf die factories austauschen, oder spezielle factories definieren zu können (zur zeit alles zentral über Tfactory.DoSomething (class procedure), würde ich das ganze gerne auf ein interface umstellen.

Das interface soll ca so aussehen, für den Nutzr reicht das an funktionalität...
Delphi-Quellcode:
 type
Ifactory<T: Iinterface> = Interface
    ['{2DA05708-FB1B-426E-8FED-02A46A7F57B7}']
    Procedure Reg(Cl: Tclass); Overload;
    Procedure Reg(Classes: Array Of TClass); Overload;
    Procedure UnReg(Cl: TClass); Overload;
    Procedure UnReg(Classes: Array Of TClass); Overload;
    // Function New<T: Iinterface>(Classname: String; Parameters: Array Of Tvalue;
    // Customconstructorname: String = 'create'): T; Overload;
    // Function New<T: Iinterface>(Classname: String): T; Overload;//<<not possible
    Function IsRegistered(Classname: String): Boolean;
    // Function Getclass(Classname: String): TClass; Overload;
  End;
Das Problem dabei ist die Nutzung der Constraints/Parameterisierung (NEW), die in der konkreten Klasse bisher ganz gut funktioniert und auch sehr nützlich ist..
>>"Interface methods must not have parameterized methods"
Wenn einer Tips hat, wie man das am besten umbaut / flexibel hält //und obige fehlermeldung elegant umschifft, wäre ich sehr dankbar.

Merci

Sequitar 23. Mär 2018 00:30

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Lösung soweit:
Delphi-Quellcode:
interface
Type
  Tfactory = Class(TInterfacedObject)
  Strict Private
    Class Function Getclass(Classname: String; Out Index: Tcount;
      Out Constr: Pointer): TClass; Overload; Static;
    Class Var Fclassregister: Iclassregister;
    // implementation of ifactory interface
  Public
    Class Constructor Create; Reintroduce;
    Class Destructor Destroy; Reintroduce;
    // Class Procedure Reg(Cl: TClass; Aconstr: Pointer = Nil); Overload; Virtual;
    Class Procedure Reg(Cl: Tclass); Overload; Static;
    Class Procedure Reg(Classes: Array Of TClass); Overload; Static;
    Class Procedure UnReg(Cl: TClass); Overload; Static;
    Class Procedure UnReg(Classes: Array Of TClass); Overload; Static;
    Class Procedure Unregall; Virtual;
    Class Function New<T: Iinterface>(Classname: String;
      Parameters: Array Of Tvalue; Customconstructorname: String = 'create')
      : T; Overload;
    Class Function New<T: Iinterface>(Classname: String): T; Overload;
    Class Function IsRegistered(Classname: String): Boolean; Virtual;
    Class Function Getclass(Classname: String): TClass; Overload; Static;
    Class Function GetDecendents(Classname: String;
      Childrenonly: Boolean = True): Tstringlist; Static;
  End;
 {
    ============================================================================
    Instantiated factory and interface
    ============================================================================
  }
  Ifactory<T: Iinterface> = Interface
    ['{2DA05708-FB1B-426E-8FED-02A46A7F57B7}']
    Procedure Reg(Cl: Tclass); Overload;
    Procedure Reg(Classes: Array Of Tclass); Overload;
    Procedure UnReg(Cl: Tinterfacedclass); Overload;
    Procedure UnReg(Classes: Array Of Tclass); Overload;
    Function New(Classname: String; Parameters: Array Of Tvalue;
      Customconstructorname: String = 'create'): T; Overload;
    Function New(Classname: String): T; Overload;
    Function IsRegistered(Classname: String): Boolean;
    // Function Getclass(Classname: String): TClass; Overload;
  End;

  // instantiable Tfactory class
  TFactory<T: Iinterface> = Class(Tfactory, Ifactory<T>)
  Private
    Procedure Reg(Cl: Tclass); Overload;
    Procedure Reg(Classes: Array Of Tclass); Overload;
    Procedure UnReg(Cl: Tinterfacedclass); Overload;
    Procedure UnReg(Classes: Array Of Tclass); Overload;
    Function New(Classname: String; Parameters: Array Of Tvalue;
      Customconstructorname: String = 'create'): T; Overload;
    Function New(Classname: String): T; Overload;
    Function IsRegistered(Classname: String): Boolean;
  End;

 {
    ============================================================================
   A sample class to be created
    ============================================================================

 Ihello = Interface

    Procedure Hello;
  End;

  Thelloworld = Class(TInterfacedObject, Ihello)
    Procedure Hello;
  End;
  }

 implementation
//Base class and class register
{...}
Class Function Tfactory.New<T>(Classname: String; Parameters: Array Of Tvalue;
  Customconstructorname: String = 'create'): T;
  Var
    Ctx: TRttiContext;
    Method: Trttimethod;
    Aclass: Tclass;
    Atype: Trttitype;
  Begin
    Result := Nil;
    Try
      Aclass := Getclass(Classname);
      Atype := Ctx.GetType(Aclass);
      Method := Atype.GetMethod(Customconstructorname);
      If Assigned(Method)
      Then
        Result := Method.Invoke(Aclass, Parameters).AsType<T>;
    Except
      On E: Exception Do
        Showmessage(Self.Classname + ': Error constructing <' + Aclass.ClassName
          + '> - ' + E.Message + '.')
    End;
  End;

{...}

//instantiated
Function TFactory<T>.IsRegistered(Classname: String): Boolean;
  Begin
    Result := Inherited IsRegistered(Classname);
  End;

Function TFactory<T>.New(Classname: String; Parameters: Array Of Tvalue;
  Customconstructorname: String): T;
  Begin
    Result := Inherited New<T>(Classname, Parameters, Customconstructorname)
  End;

Function TFactory<T>.New(Classname: String): T;
  Begin
    Result := Inherited New<T>(Classname);
  End;

Procedure TFactory<T>.Reg(Cl: Tclass);
  Begin
    Inherited Reg(Cl);
  End;

Procedure TFactory<T>.Reg(Classes: Array Of Tclass);
  Begin
    Inherited Reg(Classes);
  End;

Procedure TFactory<T>.UnReg(Cl: Tinterfacedclass);
  Begin
    Inherited UnReg(Cl);
  End;

Procedure TFactory<T>.UnReg(Classes: Array Of Tclass);
  Begin
    Inherited UnReg(Classes);
  End;

wenn ich die class function normal aufrufe, kein problem. Leider knallts bei folgendem aufruf;

Delphi-Quellcode:
Procedure Testfactoryinterfacedconstraint;
  Var
    Factory: Ifactory<Ihello>; // beliebiges interface was von der zu generierenden klasse implementiert ist
    X: Ihello;//result object
  Begin
    Factory := TFactory<Ihello>.Create;
    Factory.Reg([Thelloworld, TAnotherHelloObject]); //zentral registriert
    X := Factory.New('thelloworld');//<< hier wird also obige function new aufgerufen, die das dann an die class function weiterleited.
//in der class function knallts dann an der folgenden stelle: (aclass korrekt gefunden,ebenso wie method)
{ If Assigned(Method)
      Then
        Result := Method.Invoke(Aclass, Parameters).AsType<T>; //hier beim ASType<T> conversion prozess>>invalid type cast}
    X.Hello;

  End;
Wieso wird denn da ein Typecast error ausgeworfen??





>>>>>Nachtrag: Der typecast lag an der fehlenden Interface identifikation via GUID.
Kann mir das "method.invoke().astype<t> keine entsprechende fehlermeldung ausgeben, wenn keine GUID oder ein nicht unterstütztes Interface angegeben? So ählnlich wie bei dem AS und IS operator?

Und kann ich evlt verhindern, dass mit
Delphi-Quellcode:
 ifactory<t:interface>.reg(aclass:tclass)
eine Klasse registriert wird, die T nicht unterstützt?

Sequitar 24. Mär 2018 09:56

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Delphi-Quellcode:
 
 Var
    Guid: Tguid;
begin
{...}   
Guid := GetTypeData(TypeInfo(T))^.Guid;
      If Not Supports(Cl, Guid)
{...}
end;

freimatz 26. Mär 2018 14:01

AW: Custom Constructor /DI bei factory-basierter Objekterstellung
 
Führst Du Selbstgespräche? :wink:

Warum verwendest Du denn keinen existierenden DI-Container wie Spring4D?


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:41 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz