![]() |
Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Nun muss ich doch nochmals mein Thema aufgreifen und um Rat fragen. Ich sitze glaub irgendwie auf dem Schlauch und habe echt keine Ahnung, wie ich das Problem mittel Dependecy Injection lösen kann. Ich habe mal zwei Beispiele:
![]() Aber ich finde einfach keine sinnvolle Lösung, wie ich die obigen Probleme lösen kann. Ich komme leider immer wieder zu dem Punkt, dass ich auf den Service Locator zurückgreifen muss, um dynamisch eine beliebige Anzahl an Objekten erzeugen zu können. Hier noch ein wenig Code, dass wir nicht nur über dieses total abstrakte Gelaber diskutieren müssen :mrgreen:
Delphi-Quellcode:
Oder habe ich überhaupt etwas falsch verstanden? Ich dreh mich gerade gedankelich leider nur noch im Kreis... :wall:
function TUserFinder.Find(const Username: string): IList<IUser>;
var Query : IDbQuery; begin Query := FDatabase.GetQuery(); Query.SQL := 'SELECT * FROM Users WHERE username = :username'; Query.Parameters.Add(':username', Username); Query.Open(); try Result := TList<IUser>.Create(); for each Dataset in Query.Results do begin // // User erzeugen ??? // Result.Add(User); end; finally Query.Close(); end; end; |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Du brauchst hier eine Fabrikmethode oder eine Factory.
Delphi-Quellcode:
Mit einer Factory sieht das so aus:
// Fabrikmethode
// darf auch virtuell sein, damit man die implementierende Klasse von IUser // ändern kann function TUserFinder.CreateUser(UserID:Integer):IUser; begin Result := TUser.Create(UserID); end; function TUserFinder.Find(const Username: string): IList<IUser>; var Query : IDbQuery; begin Query := FDatabase.GetQuery(); Query.SQL := 'SELECT * FROM Users WHERE username = :username'; Query.Parameters.Add(':username', Username); Query.Open(); try Result := TList<IUser>.Create(); for each Dataset in Query.Results do begin // User erzeugen mit Fabrikmethode User := CreateUser(Query['IdUser']); User.Username := Username; User.LastLogon := Query['LastLogon']; ... Result.Add(User); end; finally Query.Close(); end; end;
Delphi-Quellcode:
Ein Service Locator ist ja auch eine Art Factory.
TUserFactory = class(TObject)
public class function CreateUser(UserID:integer):IUser;virtual; end //=============== // Jenachdem ob nur in der Find-Methode neue IUser-Objekte erzeugt werden müssen, // oder ob noch andere Methoden die Factory benötigen // übergibt man die Factory als Parameter oder als Property der Klasse TUserFinder function TUserFinder.Find(const Username: string; UF:TUserFactory): IList<IUser>; var Query : IDbQuery; begin Query := FDatabase.GetQuery(); Query.SQL := 'SELECT * FROM Users WHERE username = :username'; Query.Parameters.Add(':username', Username); Query.Open(); try Result := TList<IUser>.Create(); for each Dataset in Query.Results do begin // User erzeugen mit Factory User := UF.CreateUser(Query['IdUser']); User.Username := Username; User.LastLogon := Query['LastLogon']; ... Result.Add(User); end; finally Query.Close(); end; end; Allerdings kann ein Service Locator ALLE Arten von Objekten erzeugen, während eine Factory auf eine bestimmte Gruppe von Objekten begrenzt ist. Wenn man eine Factory wie oben im Beispiel übergibt ("injected"), dann bleibt der Code weiterhin testbar und sauber. |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Gib deinem TUserFinder (oder einem seiner Vorfahren) einfach eine Referenz auf den passen DI-Container mit.
Wenn du den TUserFinder per DI erzeugst, wäre sogar kaum eine Änderung an Code nötig. Da du diese Eiegenschaft ja als Dependency markieren kannst. :-) Wenn du ganz faul bist, kannst du ja gleich die IUserFactory als Depency-Property deklarieren.
Delphi-Quellcode:
Das problem mit Delphi und DI ist natürlich, dass Delphi keinen Garbage Collector hat. Ohne den ist das Ganze immer sau frickelig.
function TUserFinder.Find(const Username: string): IList<IUser>;
var Query : IDbQuery; userFactory : IUserFactory; begin Query := FDatabase.GetQuery(); Query.SQL := 'SELECT * FROM Users WHERE username = :username'; Query.Parameters.Add(':username', Username); Query.Open(); try userFactory := Container.Resolve<IUserFactory>(); Result := TList<IUser>.Create(); for each Dataset in Query.Results do begin // nimmsu halt die Factory aus'm Container User := userFactory.CreateUser(Query['IdUser']); User.LastLogon := Query['LastLogon']; ... Result.Add(User); end; finally Query.Close(); end; end; Eigentlich kann man in Delphi nur Interfaces in so einen DI-Container werfen. Da du ja nie weißt, ob du eine neue Instanz bekommst, oder ob du eine nereits registrierte Instanz kriegst. |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Danke schon mal für die Tipps. Jo, das dachte ich mir schon, aber ich habe damit noch ein mehr oder weniger gravierendes Problem!
@sx2008:
Delphi-Quellcode:
Hier wird das TUser-Objekt leider nicht vom DI-Container erzeugt, d.h. es werden auch keinerlei Abhängigkeiten aufgelöst. Das ist schlecht. Daher ist die Lösung von Elvis mehr oder weniger besser.
// Fabrikmethode
// darf auch virtuell sein, damit man die implementierende Klasse von IUser // ändern kann function TUserFinder.CreateUser(UserID:Integer):IUser; begin Result := TUser.Create(UserID); end; @Elvis: Bei deiner Lösung habe ich das Problem, dass in jeder Unit, in der ich Objekte erzeuge, der ServiceLocator bekannt sein muss. Ich dachte, dass diese Abhängigkeit eben nicht entstehen sollte. Aber wie schon gesagt, ich bin bisher auf noch keinen grünen Zweig gekommen, der diese Abhängigkeit eleminiert. Ich zweifle auch stark daran, dass ich diese Abhängikeit entfernen kann. Ich würde je gewünschten Typen eine entsprechende Factory basteln. Den ServiceLocator überall einzubauen wäre eher schlecht für die Lesbarkeit. |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Zitat:
Wenn du jetzt den TUserFinder durch den DI-Container hochziehst, wird er dort eine passende Instanz von der Factory reinstecken. :-) Ich selbst habe oft neben den ctor parametern und Properties, die vom DI autom. gefüllt werden, außerdem noch oft den Container in einem Vorfahren deklariert. Je nach use-case hat man sonst zu viele unnütze Dependencies, die aufgelöst werden müssen, aber in der Lebenszeit des Objektes gar nicht benutzt werden. Muss man halt abwägen... |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Zitat:
Zitat:
|
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Zitat:
Hast du ihn direkt aus einem DIC gezogen, oder ist er eine Dependency einer anderen Klasse? Ist er eine Dependency, dann würde der DIC ja autom. alles machen. Wenn nicht, dann mussu den selbst aus dem Container hochziehen. Hier mal ein Bleistift, damit wir von der gleichen Sache reden. (Ich nehme einfach an, dass dein DI-Framework [Dependency] als Markierung für Abhängigkeiten verwendet)
Delphi-Quellcode:
Du hast also irgendeine Klasse, die einen IUserFinder als Property nutzt.
type
TSomeClass = class protected [Dependency] property UserFinder : IUserFinder...; public function FindUsers(...) : IList<TUser>; ... TUserFinder = class(IUserFinder) protected [Dependency] property UserFactory : IUserFactory...; Der wiederum hat eine Property UserFactory. Würdest du jetzt zum Beispiel das hier machen...
Delphi-Quellcode:
...würde der DIC:
var miep := container.Resolve<TSomeClass>();
miep.FindUsers(...)
Ich habe da schon ganz schlimme Dinge gesehen, bei denen nicht bedacht wurde was für einen Rattenschwanz das nach sich ziehen kann. In .Net ist das einfach. Da nimmt man Post# und kann sich (wie du erwähntest) per AOP piepe-einfach die Depencies zu Lazy-Loaded ändern. In Delphi hassu da keinen, der dir da hilft... Der Sinn von DI ist ja nicht, dass überall alles von alleine geht (was ja schlecht möglich ist ;-) ), man soll die Möglichkeit haben, Implementierungen von außen ändern zu können. (Testbarkeit, Mocks!) Das geht genauso, wenn man einen Container durchreicht. (Wobei der Container das durchreichen erledigt) |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Ich habe mir die Sache nun weiter gründlich durch den Kopf gehen lassen und glaube, dass ich nun mein Problem besser formulieren kann. Dein Beispiel ist gut gewählt und ich würde vorschlagen, dass wir dabei bleiben, wenn wir weiterhin über das Thema diskutieren. Dass die Dependencies entsprechend aufgelöst werden ist ja mein Wunsch, aber scheinbar ist es so, dass nur "1:1-Abhängigkeiten" aufgelöst werden. Es gehen auch 1:N-Beziehungen, aber nur mit einem vorher fest gewählten N -- ich meine damit, dass es fixe N Properties geben muss, die dann entsprechend gefüllt werden. Folgendes klappt scheinbar nicht:
Delphi-Quellcode:
Ich will mit der 10 andeuten, dass 10 User-Objekte beim Erzeugen des Objekts injiziert werden sollen -- wobei ich doch ganz gerne die Zahl parametrisiert haben wollen würde, sodass ich eigentlich NIE auf den Service Locator zurückgreifen muss. Aber ich wüsste selbst nicht, wie ich diese Problem lösen kann, daher genau dieser Thread hier ;)
TUserFactory = class
private [Dependency(10)] FUserList : IList<IUser>; end; Daraus kommt für mich eigentlich nur ein Schluss bei raus: ich *muss* auf den Service Locator immer zugreifen, da ich nur sehr selten eine Anwendung habe, die keine Daten à la User oder Produkte aus einer DB lädt. Nutze ich für die Erstellung einer vorher nicht bekannten, beliebig großen Menge an Objekten (User, Produkte etc.) das Factory-Pattern, so muss jede Factory auf den Service Locator zurückgreifen und ist abhängig davon. Klar, die Abhängigkeit ist überschaubar und der Gewinn durch den DI-Container, der die Abhängigkeiten der erstellen Objekte entsprechend auflöst, ist echt groß, aber hier könnte man fast noch ein Stückchen mehr nachbessern und den DI-Container optimieren. Aber das ist alles nur Wunschdenken, ohne eine Idee eines eigenen Ansatzes :stupid: |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Man muss beim Programmieren aufpassen, dass man nicht dogmatisch wird.
Sicher muss eine gewisse Eleganz gewährleistet sein, damit man an dem Code arbeiten will. Keine Frage! DI und property/ctor- Injection ermöglicht es dir für viele Business-Klassen fast ohne direkte Berührung mit dem DIC auszukommen. Aber wie mit allem im Leben: Wenn man es übertreibt, wird's frickelig und kompliziert. Dogma und Scheuklappen haben in der Software-Entwicklung nix zu suchen, das ist das Betätigungsfeld für Pfarrer, Astrologen und Homöopathen... |
AW: Dependency Injection vs Service Locator -- dynamisches Erzeugung von Objekten
Wieso findet dann Philosophie seinen Platz in der Programmierung?! :stupid:
Ne, Spass bei Seite... Habe glaub komplett verstanden, wie der Hase läuft. Danke für die (Er)Klärung :thumb: |
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:22 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz