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.
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.
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!).
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.
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
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
Warum nicht
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
A
abhängig ist, werden diese Informationen in A.Dependency
gesammelt.Warum nicht
X.Dependency
separat registrieren) IoC Container
Lösung 2 ist nur ein Rohstoff. Wenn es jedoch stichhaltige Argumente dagegen gibt, wäre der beschreibende Kommentar dankbar
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 !!!!)
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.
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