37 Stimmen

Die Verwendung von Dependency Injection-Frameworks für Klassen mit vielen Abhängigkeiten

Ich habe verschiedene Dependency Injection-Frameworks für .NET angeschaut, da ich denke, dass das Projekt, an dem ich arbeite, davon sehr profitieren würde. Auch wenn ich denke, dass ich ein gutes Verständnis von den Fähigkeiten dieser Frameworks habe, bin ich mir immer noch nicht ganz im Klaren darüber, wie man sie am besten in ein großes System integriert. Die meisten Demos (verständlicherweise) beziehen sich auf ziemlich einfache Klassen, die ein oder zwei Abhängigkeiten haben.

Ich habe drei Fragen...

Erstens, wie gehst du mit diesen üblichen, aber uninteressanten Abhängigkeiten um, z.B. ILog, IApplicationSettings, IPermissions, IAudit. Es scheint übertrieben zu sein, dass jede Klasse diese als Parameter in ihrem Konstruktor hat. Wäre es besser, eine statische Instanz des DI-Containers zu verwenden, um sie abzurufen, wenn sie benötigt werden?

MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings)
// ... versus ...
ILog log = DIContainer.Get();

Zweitens, wie gehst du mit Abhängigkeiten um, die eventuell verwendet werden könnten, aber teuer in der Erstellung sind. Zum Beispiel - eine Klasse könnte von einem ICDBurner-Interface abhängig sein, möchte aber nicht, dass die konkrete Implementierung erstellt wird, es sei denn, die CD-Brennfunktion tatsächlich verwendet wird. Gibst du Interfaces an Factories weiter (z.B. ICDBurnerFactory) im Konstruktor, oder greifst du auch hier auf eine statische Methode zurück, um direkt auf den DI-Container zuzugreifen und ihn dann abzurufen, wenn er benötigt wird?

Drittens, angenommen, du hast eine umfangreiche Windows Forms-Anwendung, in der das GUI-Top-Level-Komponente (z.B. MainForm) das übergeordnete Element von potenziell Hunderten von Unterpanelen oder Modalformularen ist, von denen jedes mehrere Abhängigkeiten haben kann. Bedeutet dies, dass MainForm so eingerichtet sein sollte, dass es alle Abhängigkeiten seiner Kinder enthält? Und wenn ja, würde das nicht dazu führen, dass sich ein riesiges, selbst aufblasendes Monster bildet, das jede einzelne Klasse konstruiert, die es jemals brauchen könnte, sobald du MainForm erstellst, und dabei Zeit und Speicher verschwendet?

26voto

Nikola Malovic Punkte 1200

Nun, während Sie dies wie in anderen Antworten beschrieben tun können, glaube ich, dass es eine wichtigere Frage zu Ihrem Beispiel gibt, nämlich dass Sie wahrscheinlich das SRP-Prinzip verletzen, wenn eine Klasse viele Abhängigkeiten hat.

Was ich in Ihrem Beispiel in Betracht ziehen würde, ist die Aufteilung der Klasse in ein paar stimmigere Klassen mit fokussierten Anliegen, wodurch die Anzahl ihrer Abhängigkeiten sinken würde.

Nikolas Gesetz von SRP und DI

"Jede Klasse mit mehr als 3 Abhängigkeiten sollte auf SRP-Verletzung überprüft werden"

(Um eine lange Antwort zu vermeiden, habe ich ausführlich meine Antworten in einem IoC und SRP Blog-Beitrag gepostet)

6voto

Maurice Punkte 27462

Erstens: Fügen Sie die einfachen Abhängigkeiten nach Bedarf zu Ihrem Konstruktor hinzu. Es ist nicht notwendig, jeden Typ zu jedem Konstruktor hinzuzufügen, fügen Sie einfach diejenigen hinzu, die Sie benötigen. Benötigen Sie einen weiteren Typ, erweitern Sie einfach den Konstruktor. Die Leistung sollte kein großes Thema sein, da die meisten dieser Typen wahrscheinlich Singletons sind und daher nach dem ersten Aufruf bereits erstellt wurden. Verwenden Sie keinen statischen DI-Container, um andere Objekte zu erstellen. Fügen Sie stattdessen dem DI-Container sich selbst hinzu, sodass er sich selbst als Abhängigkeit auflösen kann. Also etwas wie dies (unter der Annahme von Unity im Moment)

IUnityContainer container = new UnityContainer();
container.RegisterInstance(container);

Auf diese Weise können Sie einfach eine Abhängigkeit von IUnityContainer hinzufügen und diese verwenden, um teure oder selten benötigte Objekte zu erstellen. Der Hauptvorteil besteht darin, dass es beim Unittesten viel einfacher ist, da keine statischen Abhängigkeiten vorhanden sind.

Zweitens: Es ist nicht erforderlich, eine Factory-Klasse zu übergeben. Mit der oben genannten Technik können Sie den DI-Container selbst verwenden, um teure Objekte bei Bedarf zu erstellen.

Drittens: Fügen Sie den DI-Container und die leichten Singleton-Abhängigkeiten zum Hauptformular hinzu und erstellen Sie den Rest über den DI-Container bei Bedarf. Es erfordert etwas mehr Code, aber wie Sie sagten, die Startkosten und der Speicherverbrauch des Hauptformulars würden in die Höhe schießen, wenn Sie alles zur Startzeit erstellen.

4voto

wsorenson Punkte 5263

Erstens:

Sie könnten diese Objekte bei Bedarf als Mitglieder einfügen, anstatt sie im Konstruktor zu haben. Auf diese Weise müssen Sie den Konstruktor nicht ändern, wenn sich Ihre Verwendung ändert, und Sie müssen auch kein Static verwenden.

Zweitens:

Geben Sie einen Builder oder eine Factory ein.

Drittens:

Jede Klasse sollte nur die Abhängigkeiten haben, die sie selbst benötigt. Subklassen sollten mit ihren eigenen spezifischen Abhängigkeiten injiziert werden.

4voto

Lasse V. Karlsen Punkte 364542

Ich habe einen ähnlichen Fall bezogen auf das "teuer zu erstellen und möglicherweise verwendet wird", wo in meiner eigenen IoC-Implementierung, füge ich automatische Unterstützung für Factory-Services hinzu.

Grundsätzlich anstelle dessen:

public SomeService(ICDBurner burner)
{
}

würdest du dies tun:

public SomeService(IServiceFactory burnerFactory)
{
}

ICDBurner burner = burnerFactory.Create();

Dies hat zwei Vorteile:

  • Im Hintergrund wird der Service-Container, der Ihren Service aufgelöst hat, auch verwendet, um den Brenner aufzulösen, wenn und wann er angefordert wird
  • Dies lindert die Bedenken, die ich zuvor in diesem Fall gesehen habe, wo die typische Methode wäre, den Service-Container selbst als Parameter an Ihren Dienst einzuspeisen, im Grunde zu sagen "Dieser Dienst benötigt andere Dienste, aber ich werde Ihnen nicht leicht sagen, welche"

Das Factory-Objekt ist recht einfach zu erstellen und löst viele Probleme.

Hier ist meine Factory-Klasse:

...

Grundsätzlich, wenn ich den Service-Container aus meiner Service-Container-Builder-Klasse erstelle, werden alle Service-Registrierungen automatisch einen anderen Co-Service erhalten, der IServiceFactory für diesen Service implementiert, es sei denn, der Programmierer hat sich explizit für diesen Service registriert. Der obige Service wird dann verwendet, wobei ein Parameter das Policy angibt (das kann null sein, wenn Policies nicht verwendet werden).

Das ermöglicht mir dies zu tun:

var builder = new ServiceContainerBuilder();
builder.Register()
    .From.ConcreteType();

using (var container = builder.Build())
{
    using (var factory = container.Resolve>())
    {
        using (var service = factory.Instance.Create())
        {
            service.Instance.DoSomethingAwesomeHere();
        }
    }
}

Natürlich wäre ein typischerer Gebrauch mit Ihrem CD-Brenner-Objekt. Im obigen Code würde ich jedoch den Service auflösen, aber es ist eine Veranschaulichung dessen, was passiert.

Also mit Ihrem CD-Brenner-Dienst stattdessen:

...

innerhalb des Dienstes könnten Sie nun einen Dienst haben, einen Factory-Dienst, der weiß, wie man Ihren CD-Brenner-Service bei Bedarf auflöst. Dies ist aus folgenden Gründen nützlich:

  • Sie möchten möglicherweise mehr als einen Dienst gleichzeitig auflösen (zwei Discs gleichzeitig brennen?)
  • Sie benötigen es möglicherweise nicht und es könnte teuer sein zu erstellen, also lösen Sie es nur wenn nötig auf
  • Sie müssen möglicherweise mehrmals auflösen, entsorgen, auflisten, entsorgen, anstatt zu hoffen/zu versuchen, eine bestehende Dienstinstanz aufzuräumen
  • Sie kennzeichnen auch in Ihrem Konstruktor, welche Dienste Sie brauchen und welche Sie möglicherweise benötigen

Hier sind zwei gleichzeitig:

...

Hier sind zwei nacheinander, ohne den gleichen Dienst wiederzuverwenden:

...

2voto

Peter Mounce Punkte 3976

Erste:

Sie könnten es angehen, indem Sie einen Container erstellen, um Ihre "uninteressanten" Abhängigkeiten (ILog, ICache, IApplicationSettings, etc.) zu halten, und Konstruktorinjektion verwenden, um diese einzufügen, dann intern im Konstruktor die Felder des Dienstes aus container.Resolve() befüllen? Ich bin mir nicht sicher, ob mir das gefällt, aber nun ja, es ist eine Möglichkeit.

Alternativ könnten Sie das neue IServiceLocator-Common-Interface (http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx) anstatt der Injektion der Abhängigkeiten verwenden?

Zweite:

Sie könnten Setter-Injektion für die optionalen/auf Anfrage benötigten Abhängigkeiten verwenden? Ich denke, ich würde Fabriken injizieren und von dort aus bei Bedarf neu erzeugen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X