wake-up-neo.net

Wie vermeidet man den Wahnsinn des Abhängigkeitsinjektors?

Ich finde, dass meine Konstrukteure so aussehen:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

mit immer mehr Parameterliste. Da "Container" mein Abhängigkeitsinjektionscontainer ist, kann ich dies nicht einfach tun:

public MyClass(Container con)

für jede Klasse? Was sind die Nachteile? Wenn ich das tue, fühle ich mich so, als würde ich eine verklärte Statik verwenden. Teilen Sie bitte Ihre Gedanken zu IoC und Dependency Injection-Wahnsinn mit.

265
JP Richardson

Sie haben Recht, wenn Sie den Container als Service Locator verwenden, handelt es sich mehr oder weniger um eine verherrlichte statische Fabrik. Aus vielen Gründen ich halte dies für ein Anti-Muster .

Einer der wunderbaren Vorteile von Constructor Injection besteht darin, dass Verstöße gegen das Prinzip der Einzelverantwortung augenfällig werden.

In diesem Fall ist es Zeit für mgestaltung der Fassadendienste . Kurz gesagt, erstellen Sie eine neue, grobkörnigere Schnittstelle , die die Interaktion zwischen einigen oder allen feinkörnigen Abhängigkeiten verbirgt, die Sie derzeit benötigen.

386
Mark Seemann

Ich denke nicht, dass Ihre Klassenkonstruktoren einen Verweis auf Ihre IOC Containerperiode haben sollten. Dies stellt eine unnötige Abhängigkeit zwischen Ihrer Klasse und dem Container dar (die Art der Abhängigkeit IOC versucht zu vermeiden!). 

57
derivation

Die Schwierigkeit der Parameterübergabe ist nicht das Problem. Das Problem ist, dass Ihre Klasse zu viel tut und mehr abgebaut werden sollte.

Abhängigkeitsinjektion kann eine Frühwarnung sein, wenn Klassen zu groß werden, insbesondere wegen des zunehmenden Schmerzes beim Passieren in allen Abhängigkeiten.

24
kyoryu

Ich stieß auf eine ähnliche Frage über die konstruktorbasierte Abhängigkeit von Injection und wie komplex sie in allen Abhängigkeiten werden sollte.

Ein Ansatz, den ich in der Vergangenheit verwendet habe, ist die Verwendung des Anwendungsfassadenmusters unter Verwendung einer Serviceschicht. Dies hätte eine grobe API. Wenn dieser Dienst von Repositorys abhängt, würde er eine Setter-Injektion der privaten Eigenschaften verwenden. Dies erfordert das Erstellen einer abstrakten Fabrik und das Verschieben der Logik der Erstellung der Repositorys in eine Fabrik.

Ausführlichen Code mit Erläuterungen finden Sie hier

Best Practices für IoC in der komplexen Serviceebene

3
samsur

Problem :  

1) Konstruktor mit ständig wachsender Parameterliste.

2) Wenn die Klasse vererbt wird (Ex: RepositoryBase), führt das Ändern der Signatur des Konstruktors Zu Änderungen in den abgeleiteten Klassen.

Lösung 1

IoC Container an Konstruktor übergeben 

Warum

  • Keine ständig wachsende Parameterliste
  • Die Signatur des Konstruktors wird einfach

Warum nicht

  • Macht den Unterricht eng an den IoC-Container gekoppelt. (Dies führt zu Problemen, wenn 1. Sie diese Klasse in anderen Projekten verwenden möchten, in denen andere IoC-Container verwendet werden. 2. Sie sich entscheiden, den IoC-Container zu ändern.)
  • Macht die Klasse weniger beschreibend. (Sie können den Klassenkonstruktor nicht wirklich betrachten und sagen, was er zum Funktionieren benötigt.)
  • Klasse kann auf alle Dienste zugreifen.

Lösung 2

Erstellen Sie eine Klasse, die alle Dienste gruppiert, und übergeben Sie sie an den Konstruktor

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Abgeleitete Klasse 

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Warum

  • Das Hinzufügen einer neuen Abhängigkeit zu einer Klasse hat keine Auswirkungen auf abgeleitete Klassen 
  • Klasse ist Agnostiker von IoC Container
  • Klasse ist deskriptiv (in Bezug auf ihre Abhängigkeiten). Wenn Sie wissen möchten, von welcher Klasse A abhängig ist, werden diese Informationen in A.Dependency gesammelt.
  • Die Signatur des Konstruktors wird einfach

Warum nicht

  • müssen zusätzliche Klasse erstellen
  • service-Registrierung wird komplex (Sie müssen jeden X.Dependency separat registrieren)
  • Konzeptionell wie Übergabe von IoC Container
  • .. 

Lösung 2 ist nur ein Rohstoff. Wenn es jedoch stichhaltige Argumente dagegen gibt, wäre der beschreibende Kommentar dankbar

1
tchelidze

Das Einspritzen des Containers ist eine Verknüpfung, die Sie letztendlich bereuen werden.

Überinjektion ist nicht das Problem, es ist in der Regel ein Symptom für andere strukturelle Mängel, vor allem die Trennung von Bedenken. Dies ist nicht ein Problem, kann jedoch viele Quellen haben, und was es so schwer zu beheben gibt, ist, dass Sie sich manchmal mit allen beschäftigen müssen (manchmal zur gleichen Zeit). 

Hier ist eine unvollständige Liste der Dinge, auf die Sie achten sollten

Schlechtes Domain-Design (Aggregat-Roots usw.)

Schlechte Trennung der Bedenken (Servicezusammenstellung, Befehle, Abfragen) Siehe CQRS und Event Sourcing.

ODER Mapper (seien Sie vorsichtig, diese Dinge können Sie in Schwierigkeiten bringen)

Modelle und andere DTOs anzeigen (Nie wiederverwenden und versuchen, sie auf ein Minimum zu beschränken !!!!)

0
Marius

Ich habe diesen ganzen Thread zweimal gelesen, und ich denke, die Leute reagieren darauf, was sie wissen, und nicht, was gefragt wird.

JPs ursprüngliche Frage sieht so aus, als würde er Objekte erstellen, indem er einen Resolver und dann eine Reihe von Klassen sendet. Wir gehen jedoch davon aus, dass diese Klassen/Objekte selbst Dienste sind und für Injektionen reif sind. Was ist, wenn sie nicht sind?

JP: Wenn Sie DI nutzen möchten und den Ruhm des Mischens der Injektion mit Kontextdaten wünschen, spricht keines dieser Muster (oder angenommene "Anti-Muster") genau das an. Es kommt tatsächlich darauf an, ein Paket zu verwenden, das Sie bei einem solchen Unterfangen unterstützt.

Container.GetSevice<MyClass>(someObject1, someObject2)

... dieses Format wird selten unterstützt. Ich glaube, dass die Schwierigkeit, eine solche Unterstützung zu programmieren, zu der miserablen Leistung, die mit der Implementierung verbunden wäre, hinzukommt, und macht sie für OpenSource-Entwickler uninteressant.

Aber es sollte gemacht werden, weil ich in der Lage sein sollte, eine Fabrik für MyClass'es zu erstellen und zu registrieren, und diese Fabrik sollte Daten/Eingaben empfangen können, die nicht als "Dienst" dienen, nur um bestanden zu werden Daten. Wenn es sich bei "Anti-Pattern" um negative Konsequenzen handelt, ist das Bestehen künstlicher Servicetypen für die Weitergabe von Daten/Modellen sicherlich negativ (gleich Ihrem Gefühl, Ihre Klassen in einen Container zu packen. Dasselbe Instinkt trifft zu).

Es gibt Rahmen, die helfen können, auch wenn sie etwas hässlich aussehen. Zum Beispiel Ninject:

Erstellen einer Instanz mit Ninject mit zusätzlichen Parametern im Konstruktor

Das ist für .NET, ist beliebt und ist nirgendwo so sauber, wie es sein sollte, aber ich bin sicher, es gibt etwas in der Sprache, in der Sie sich entscheiden.

0
Craig Brunetti

Dies ist der Ansatz, den ich benutze

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Hier ist ein grober Ansatz, wie die Injektionen ausgeführt und der Konstruktor nach dem Einfügen von Werten ausgeführt wird. Dies ist ein voll funktionsfähiges Programm.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

Ich arbeite gerade an einem Hobbyprojekt, das so funktioniert https://github.com/Jokine/ToolProject/tree/Core

0
Ronijo