Vermeiden eines verschachtelten Service-Locator-Antipatterns mit DI / Autofac

8

Ich hatte in einem früheren Spielprojekt etwas von einem praktischen Service-Locator-Anti-Pattern. Ich möchte das durch die Abhängigkeitsinjektion ersetzen. autofac sieht für mich wie der wahrscheinlichste DI-Container aus, da er relevante Funktionen zu haben scheint - aber ich kann es nicht herausfinden wie man das erreicht, wonach ich suche.

Bestehender Ansatz

Anstelle eines einzelnen Service-Locators hatte ich einen Service-Locator, der an seinen übergeordneten Service delegieren konnte (in der Tat "scoped" Services):

%Vor%

Zur Vereinfachung wurde das Spiel aus einem Baum mit komponentenbasierten Klassen aufgebaut:

%Vor%

So konnte ich (und tat) Baumstrukturen bauen wie:

%Vor%

Und jede Komponente könnte einfach den ServiceLocator aufrufen, mit dem sie konstruiert wurde, um alle benötigten Dienste zu finden.

Vorteile:

  • Komponenten müssen sich nicht darum kümmern, wer die Dienste, die sie nutzen, oder wo diese Dienste leben, implementiert. solange die Lebenszeiten dieser Dienste gleich oder größer als ihre eigenen sind.

  • Mehrere Komponenten können denselben Dienst verwenden, dieser Dienst kann jedoch nur so lange bestehen, wie er benötigt wird. Insbesondere können wir einen ganzen Teil der Hierarchie dispose (), wenn der Spieler eine Ebene verlässt, was viel einfacher ist, als Komponenten komplexe Datenstrukturen neu zu erstellen, um sie an die Idee anzupassen, dass sie sich jetzt auf einer völlig neuen Ebene befinden. p>

Nachteile:

  • Wie Mark Seeman darauf hinweist, ist Service Locator ein Anti-Pattern .

  • Einige Komponenten würden Dienstanbieter instanziieren, weil ich (der Programmierer) weiß, dass verschachtelte Komponenten diesen Dienst benötigen oder ich (der Programmierer) weiß, dass das Spiel funktionieren muss z.B AI läuft in der Spielwelt , nicht weil der Instanziierer diesen Dienst per se benötigt.

Ziel

Im Geiste von DI möchte ich das gesamte Wissen über "Service Locators" und "Scopes" von Components entfernen. Sie würden (über DI) Konstruktorparameter für jeden von ihnen konsumierten Dienst erhalten. Um dieses Wissen aus den Komponenten herauszuhalten, muss der Kompositionswurzel für jede Komponente Folgendes angeben:

  • Wird ein bestimmter Komponententyp instanziiert, wird ein neuer Bereich erstellt
  • Innerhalb dieses Bereichs, welche Dienste verfügbar sind.

Ich möchte das intuitive schreiben:

%Vor%

Und in der Lage sein, in der Komposition root das

anzugeben
  • IAudioService wird von der Audio-Klasse implementiert, und Sie sollten einen Singleton erstellen (ich kann das tun!)
  • IShootingService wird von ShootingComponent implementiert und es sollte eines davon pro Bildschirm
  • geben
  • INavigationService gemäß IShootingService

Ich muss gestehen, dass ich völlig verloren bin, wenn es um die letzten beiden geht. Ich werde hier meine zahlreichen fehlgeschlagenen Autofac-basierten Versuche nicht aufzählen, da ich über einen langen Zeitraum hinweg einige Dutzend gemacht habe und keiner von ihnen im Entferntesten funktional war. Ich habe die Dokumentation ausführlich gelesen - ich weiß, dass lebenslange Bereiche und Owned<> in dem Bereich sind, den ich betrachte, aber ich kann nicht sehen, wie man Abhängigkeiten in Abhängigkeiten transparent einfügt, wie ich es sehe - aber ich fühle, dass DI im Allgemeinen scheint genau das zu erleichtern, was ich tun will!

Wenn das gesund ist, wie kann ich das erreichen? Oder ist das nur teuflisch? Wenn ja, wie würden Sie eine solche Anwendung strukturieren, die DI sinnvoll einsetzt, um zu vermeiden, dass Objekte rekursiv umgangen werden, wenn die Lebensdauer dieser Objekte abhängig vom Kontext variiert, in dem das Objekt verwendet wird?

    
El Zorko 26.12.2013, 16:47
quelle

2 Antworten

1

LifetimeScopes klingen wie die Antwort. Ich denke, was du im Grunde tust, ist das Verbinden von Lebensbereichen mit Bildschirmen. So würden ShootingComponent und Freunde mit .InstancePerMatchingLifetimeScope("Screen") registriert werden. Der Trick besteht dann darin, dass jeder Bildschirm in einem neuen LifetimeScope erstellt wird, das als "Bildschirm" gekennzeichnet ist. Mein erster Gedanke wäre, eine Bildschirmfabrik so zu machen:

%Vor%

Das ist völlig ungeprüft, aber ich denke, das Konzept macht Sinn.

    
Jim Bolla 09.01.2014 14:11
quelle
1

Zufällig arbeite ich derzeit mit ähnlichen Anforderungen (Autofac verwenden) und hier ist das, was ich bisher herausgefunden habe:

  • Beginnen Sie zuerst mit Modulen. Sie sind eine hervorragende Möglichkeit, Ihre Abhängigkeiten und Konfigurationen zu verwalten.
  • Definieren Sie Ihre Abhängigkeiten mit geeigneten Lebensdauern: IAudioService als Singleton und IShootingService gemäß Lebensdauerumfang.
  • Stellen Sie sicher, dass Ihre lebenslangen Bereichsschnittstellen auch IDisposable implementieren, um eine ordnungsgemäße Bereinigung sicherzustellen.
  • Erstellen Sie einen Thin-Wrapper, um Ihre gesamte Lebensdauer in einem einfachen eingebetteten Framework zu verwalten: Fügen Sie Ihre Spieleplattformen zwischen den Methoden Begin () und End () ein. (So ​​habe ich es gemacht. Ich bin mir sicher, dass Sie einen besseren Weg finden, dies in Ihrer eigenen Struktur zu tun)
  • (optional) Erstellen Sie ein "Kern" -Modul, um Ihre generischen Abhängigkeiten zu behalten (d. h. IAudioService) und separate Module für andere Inseln von Abhängigkeiten (die z. B. von verschiedenen Implementierungen derselben Schnittstelle abhängig sein können)

Hier ist ein Beispiel, wie ich es gemacht habe:

%Vor%

In dem von Ihnen angegebenen Beispiel würde ich die Auflösung von AIComponent zwischen Anfangs- und Endaufrufen der obigen Klasse setzen, wenn Sie Ebenen durchlaufen.

Wie gesagt, ich bin mir sicher, dass Sie in Ihrer Entwicklungsstruktur eine bessere Möglichkeit finden werden, dies zu tun. Ich hoffe, dies gibt Ihnen die Grundidee, wie Sie es umsetzen können, vorausgesetzt, meine Erfahrung ist als eine "gute" Art, dies zu tun.

Viel Glück.

    
ziya 19.10.2014 00:17
quelle