Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Dependency Injection - zirkulare Abhängigkeiten / Lazy Init? (https://www.delphipraxis.net/200385-dependency-injection-zirkulare-abhaengigkeiten-lazy-init.html)

Sequitar 15. Apr 2019 23:10

Dependency Injection - zirkulare Abhängigkeiten / Lazy Init?
 
Hallo, ich habe mich in der letzten zeit mal intensiver mit DI beschäftigt und mir ein eigenes basisframework hierfür erstellt (Ja es gibt professionelle lösungen, weiss ich, solll aber nicht das "grosse" thema werden ^^)

Angefangen mit einer Factory und einem entsprechenden Classregister kann ich diverse Typen registrieren und über ihren Klassennamen entweder direkt oder - besser - als interface ausspucken. Damit sind zzt auch standard registierungen möglich um sowas wie
Delphi-Quellcode:
tfactory.new<iasimpleinterface>('standardclassforthis')
durch einen kürzeren aufruf von
Delphi-Quellcode:
tfactory.new<iasimpleinterface>
ausführen zu lassen.

Beim Lesen von Hodges' buch zum thema "coding in delphi" und dessen fortsetzung, konnte ich damit folgendeen zum sinnvollen leben erwecken (test klasse, fragt nicht nach der sinnhaftigkeit, es geht ums prinzip):
Delphi-Quellcode:
 
Iweapon = Interface
    ['{EC1ED609-7896-4075-B9BF-51CD70E821B5}']
    Procedure Punch;
  End;

  Iknight = Interface
    ['{7C65730B-93A9-4F92-AB6E-E192E31199E1}']
    Procedure Attack;
    Procedure Setweapon(Weapon: Iweapon);
    Function Getweapon: Iweapon;
    Property Weapon: IWeapon Read Getweapon Write Setweapon;
  End;

  Tsword = Class(Tinterfacedobject, Iweapon)
    Procedure Punch;
  End;

  Tfist = Class(Tinterfacedobject, Iweapon)
    Procedure Punch;
  End;

  TKnight = Class(Tinterfacedobject, Iknight)
  Private
    [Inject('tfist')]
    FWeapon2: IWeapon;
    [Inject('tsword')]
    FWeapon: IWeapon;
    // [Inject('tfist')]
    // does only work on variables and fields
    Property Weapon2: Iweapon Read Fweapon2;
    Procedure Setweapon(Weapon: Iweapon);
    Function Getweapon: Iweapon;

  Public
    Procedure Attack;
  End;
.
Das ganze wird mit normaler RTTI typinfo über felder attribute und so gelöst..
Ausführen liesse sich das dann zb so:

Delphi-Quellcode:
Procedure Knighttest;
Begin
  Tfactory.Regdefault<Iknight>(Tknight);
  Tfactory.Reg<Iweapon>(Tfist);
  Tfactory.Regdefault<Iweapon>(Tsword);
  Var
  Knight := Tfactory.New<Iknight>;
  Knight.Attack;
  // Knight.Weapon := Tfist.Create;
  Knight.Attack;
  Tfactory.UnReg([Tknight, Tsword, Tfist]);
End;
.

So weit so gut. Nur was mache ich wenn ich dem ersten edlen Ritter einen freund oder eine ganze mannschaft bereitstellen will?:
Dabei bin ich auf das problem von zirkularen abhängigkeiten gestoßen..:

Delphi-Quellcode:
//...
 TKnightcompanion = Class(Tinterfacedobject, Iknight)
  Private
    [Inject('tsword')]   // does only work on variables and fields, but ok
    FWeapon: IWeapon;
   
   [Inject('tknight')]
    [Weak]
    Fcompanion: Iknight; //!!!! HIER.: circular reference.
 

    Property Weapon: Iweapon Read Fweapon;
    Procedure Setweapon(Weapon: Iweapon);
    Function Getweapon: Iweapon;

  Public
    Procedure Attack;
  End;
Wenn ich also, bisher, den Ritter allein kämpfen lasse, funktioniert alles einwandfrei. Es ist aber offensichtlich (hoffentlch), dass beim automatisierten Erstellen und Einfügen eines Freundes (zur Zeit gleicher oder abgeleiteter Klasse) über die Factory oder zentrale Registry eine endlose Recursion bildet.


Lösungsideen:
[weak]- oder late binding, creation on demand oder sowas? Woher weiss ich welche Klasse später was braucht>>>dependency tree? (find ich auch nach langem Selbststudium schwer zu realisieren)
Kann man das "elegant" / simpel / ... lösen oder umgehen?
..

Nachtrag: Am simpelsten wäre wohl constructor / property injection. Davon wollte ich jetzt mal absehen, nachdem ich das zusammenstöpseln der objekte auch schöner hinbekam.

Danke für Eure hinweise.

Der schöne Günther 16. Apr 2019 09:32

AW: Dependency Injection - zirkulare Abhängigkeiten / Lazy Init?
 
Das hat ja mit DI an sich erst einmal gar nichts zu tun.

Es ist eine zirkuläre Referenz zweiter Objekte aufeinander, beide arbeiten mit ARC. Während ein Garbage Collector so etwas erkennen kann ist das mit ARC nicht drin - Es sei denn man sagt "Der Companion interessiert mich nicht mehr wenn es den Ritter nicht mehr gibt" und macht die Referenz vom Companion auf den Ritter
Delphi-Quellcode:
[weak]
Den Artikel von Marco Cantu kennst du sicher schon.

Sequitar 16. Apr 2019 11:50

AW: Dependency Injection - zirkulare Abhängigkeiten / Lazy Init?
 
Hallo, danke für die Antwort.
Zitat:

Das hat ja mit DI an sich erst einmal gar nichts zu tun.
Mit dem Prinzip an sich nicht, stimmt. Vielleicht habe ich mein Problem auch falsch beschrieben.
Den Artikel kannte ich noch nicht, danke. [Weak] ist mir ein Begriff, [unsafe] bisher noch nicht. Hat aber, erstmal, seinen Dienst getan:

Delphi-Quellcode:
 TKnight = Class(Tinterfacedobject, Iknight)
  Private
    [Inject('tsword')]
    FWeapon: IWeapon;
    [Inject('tfist')]
    FWeapon2: IWeapon;
    [Inject('tknight')]
    [unsafe]//unsafe attribute. Solves problem w/ manual construction
    Fcompanion: Iknight;
{ [...]}
 
  Public
    Constructor Create(Companion: Iknight);
    Destructor Destroy; Override;
{...}
  End;

destructor tknight.destroy;
begin
fsecondknight:=nil;
inherited;
end;

procedure test;
var knight1,knight2:iknight;
begin
knight1:=tknight.create(nil);
knight2:=tknight.create(knight1);
knight1:=nil; //der Vollständigkeit halber
end;

Es geht mir aber weniger (vielleicht auch) um die normale Erstellung solcher von einander abhängigen Objekte, wie oben. Vielmehr gehts mir um das Automatisieren der Erstellung per Attribut (inject['companionclassname']). Wie kann ich festlegen, dass der zweite (n.te) KEINEN companion mehr braucht, oder der nur bei Bedarf angelegt wird, statt automatisch beim Auffinden des [inject] eine Endlosrekursion anzustoßen?

freimatz 16. Apr 2019 12:01

AW: Dependency Injection - zirkulare Abhängigkeiten / Lazy Init?
 
Hallo,
ich bin mir unsicher ob ich dich richtig verstanden habe.
Auf jeden Fall injiziert man bei DI nur Dinge die man braucht. Ein mal brauchen und mal nicht gibt es nicht. In dem Fall braucht ein Ritter keinen zweiten. Ein Ritter IST auch alleine eine Ritter. Eine Beziehung zu einem anderen Ritter muss man dann anderweitig besorgen. Ggf. könnte man eine factory reinreichen.

Sequitar 16. Apr 2019 12:17

AW: Dependency Injection - zirkulare Abhängigkeiten / Lazy Init?
 
Zitat:

Zitat von freimatz (Beitrag 1430404)
Hallo,
ich bin mir unsicher ob ich dich richtig verstanden habe.
Auf jeden Fall injiziert man bei DI nur Dinge die man braucht. Ein mal brauchen und mal nicht gibt es nicht. In dem Fall braucht ein Ritter keinen zweiten. Ein Ritter IST auch alleine eine Ritter. Eine Beziehung zu einem anderen Ritter muss man dann anderweitig besorgen. Ggf. könnte man eine factory reinreichen.

Also konzeptionelles Problem?
Gut vom Begriff her ist eine dependency ja eine Abhängigkeit, in dem Fall wäre es ja, wie du schreibst,keine Abhängigkeit, sondern eher eine Option...Insofern müsste ich da wohl von constructor oder attribut-based injection (obligatorisch) auf get/set injection (fakultativ)umsatteln. Da kann ich dann die Beziehung ja manuell herstellen.Macht eigentlich Sinn.

Ich hatte, in Bezug auf lazy init, nur gedacht, dass das auch zu automatisieren sei? Oder brauch ich das dann gar nicht?

Danke


Alle Zeitangaben in WEZ +1. Es ist jetzt 21:25 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