Ich verwende Unity in C # für die Abhängigkeitsinjektion, aber die Frage sollte für jede Sprache und jedes Framework gelten, das die Abhängigkeitsinjektion verwendet.
Ich versuche dem SOLID-Prinzip zu folgen und habe deshalb viele Abstraktionen bekommen. Aber jetzt frage ich mich, ob es eine bewährte Methode für die Anzahl der Injektionen gibt, die eine Klasse injizieren sollte.
Zum Beispiel habe ich ein Repository mit 9 Injektionen. Wird dies für einen anderen Entwickler schwer zu lesen sein?
Die Injektionen haben folgende Verantwortlichkeiten:
- IDbContextFactory - Erstellt den Kontext für die Datenbank.
- IMapper - Zuordnung von Entitäten zu Domänenmodellen.
- IClock - Abstracts DateTime.Now hilft bei Unit-Tests.
- IPerformanceFactory - misst die Ausführungszeit für bestimmte Methoden.
- ILog - Log4net zur Protokollierung.
- ICollectionWrapperFactory - Erstellt Sammlungen (die IEnumerable erweitern).
- IQueryFilterFactory - Generiert Abfragen basierend auf Eingaben, die db abfragen.
- IIdentityHelper - Ruft den angemeldeten Benutzer ab.
- IFaultFactory - Erstellen Sie verschiedene FaultExceptions (ich verwende WCF).
Ich bin nicht wirklich enttäuscht darüber, wie ich die Verantwortlichkeiten delegiert habe, aber ich mache mir langsam Sorgen um die Lesbarkeit.
Also meine Fragen:
Gibt es eine Grenze für die Anzahl der Injektionen, die eine Klasse haben sollte? Und wenn ja, wie kann man das vermeiden?
Schränken viele Injektionen die Lesbarkeit ein oder verbessern sie sie tatsächlich?
quelle
Antworten:
Zu viele Abhängigkeiten können darauf hinweisen, dass die Klasse selbst zu viel tut. Um festzustellen, ob es nicht zu viel tut:
Schau dir die Klasse selbst an. Wäre es sinnvoll, es in zwei, drei, vier aufzuteilen? Ist es insgesamt sinnvoll?
Schauen Sie sich die Arten von Abhängigkeiten an. Welche sind domänenspezifisch und welche „global“? Zum Beispiel würde ich nicht
ILog
auf der gleichen Ebene betrachten wieIQueryFilterFactory
: Die erste wäre ohnehin in den meisten Geschäftsklassen verfügbar, wenn sie die Protokollierung verwenden. Wenn Sie andererseits viele domänenspezifische Abhängigkeiten finden, kann dies darauf hinweisen, dass die Klasse zu viel tut.Schauen Sie sich die Abhängigkeiten an, die durch Werte ersetzt werden können.
Dies könnte leicht ersetzt werden, indem
DateTime.Now
es direkt an die Methoden übergeben wird, die die aktuelle Zeit kennen müssen.Wenn ich mir die tatsächlichen Abhängigkeiten anschaue, sehe ich nichts, was darauf hindeutet, dass schlimme Dinge passieren:
IDbContextFactory - Erstellt den Kontext für die Datenbank.
OK, wir befinden uns wahrscheinlich in einer Geschäftsschicht, in der Klassen mit der Datenzugriffsschicht interagieren. Sieht gut aus.
IMapper - Zuordnung von Entitäten zu Domänenmodellen.
Ohne das Gesamtbild ist es schwierig, etwas zu sagen. Es kann sein, dass die Architektur falsch ist und die Zuordnung direkt von der Datenzugriffsschicht erfolgen sollte, oder dass die Architektur vollkommen in Ordnung ist. In allen Fällen ist es sinnvoll, diese Abhängigkeit hier zu haben.
Eine andere Möglichkeit wäre, die Klasse in zwei Klassen aufzuteilen: eine für die Zuordnung, die andere für die eigentliche Geschäftslogik. Dies würde eine De-facto-Schicht erzeugen, die das BL weiter vom DAL trennt. Wenn Zuordnungen komplex sind, könnte dies eine gute Idee sein. In den meisten Fällen würde dies jedoch nur zu einer nutzlosen Komplexität führen.
IClock - Abstracts DateTime.Now hilft bei Unit-Tests.
Es ist wahrscheinlich nicht sehr nützlich, eine separate Schnittstelle (und Klasse) zu haben, um die aktuelle Zeit abzurufen. Ich würde das einfach
DateTime.Now
an die Methoden weitergeben, die die aktuelle Zeit benötigen.Eine separate Klasse kann sinnvoll sein, wenn andere Informationen wie die Zeitzonen oder die Datumsbereiche usw. vorhanden sind.
IPerformanceFactory - misst die Ausführungszeit für bestimmte Methoden.
Siehe den nächsten Punkt.
ILog - Log4net zur Protokollierung.
Solche transzendenten Funktionen sollten zum Framework gehören und die tatsächlichen Bibliotheken sollten zur Laufzeit austauschbar und konfigurierbar sein (z. B. über app.config in .NET).
Leider ist dies (noch) nicht der Fall, sodass Sie entweder eine Bibliothek auswählen und dabei bleiben oder eine Abstraktionsschicht erstellen können, um die Bibliotheken später bei Bedarf austauschen zu können. Wenn Sie speziell von der Wahl der Bibliothek unabhängig sein möchten, entscheiden Sie sich dafür. Wenn Sie ziemlich sicher sind, dass Sie die Bibliothek jahrelang weiter verwenden, fügen Sie keine Abstraktion hinzu.
Wenn die Bibliothek zu komplex für die Verwendung ist, ist ein Fassadenmuster sinnvoll.
ICollectionWrapperFactory - Erstellt Sammlungen (die IEnumerable erweitern).
Ich würde annehmen, dass dies sehr spezifische Datenstrukturen erzeugt, die von der Domänenlogik verwendet werden. Es sieht aus wie eine Utility-Klasse. Verwenden Sie stattdessen eine Klasse pro Datenstruktur mit relevanten Konstruktoren. Wenn die Initialisierungslogik etwas kompliziert ist, um in einen Konstruktor zu passen, verwenden Sie statische Factory-Methoden. Wenn die Logik noch komplexer ist, verwenden Sie das Factory- oder Builder-Muster.
IQueryFilterFactory - Generiert Abfragen basierend auf Eingaben, die db abfragen.
Warum ist das nicht in der Datenzugriffsschicht? Warum steht
Filter
im Namen ein?IIdentityHelper - Ruft den angemeldeten Benutzer ab.
Ich bin mir nicht sicher, warum es ein
Helper
Suffix gibt. In allen Fällen sind auch andere Suffixe nicht besonders explizit (IIdentityManager
?)Auf jeden Fall ist es durchaus sinnvoll, diese Abhängigkeit hier zu haben.
IFaultFactory - Erstellen Sie verschiedene FaultExceptions (ich verwende WCF).
Ist die Logik so komplex, dass ein Fabrikmuster verwendet werden muss? Warum wird dafür Dependency Injection verwendet? Würden Sie die Erstellung von Ausnahmen zwischen Produktionscode und Tests austauschen? Warum?
Ich würde versuchen, das in einfach umzugestalten
throw new FaultException(...)
. Wenn einige globale Informationen zu allen Ausnahmen hinzugefügt werden sollen, bevor sie an den Client weitergegeben werden, verfügt WCF wahrscheinlich über einen Mechanismus, mit dem Sie eine nicht behandelte Ausnahme abfangen und sie ändern und erneut an den Client senden können.Das Messen der Qualität anhand von Zahlen ist normalerweise so schlecht wie das Bezahlen mit Codezeilen, die Sie pro Monat schreiben. Möglicherweise haben Sie eine hohe Anzahl von Abhängigkeiten in einer gut gestalteten Klasse, da Sie eine beschissene Klasse mit wenigen Abhängigkeiten haben können.
Viele Abhängigkeiten erschweren es, der Logik zu folgen. Wenn die Logik schwer zu befolgen ist, tut die Klasse wahrscheinlich zu viel und sollte aufgeteilt werden.
quelle
Dies ist ein klassisches Beispiel für DI, das Ihnen sagt, dass Ihre Klasse wahrscheinlich zu groß wird, um eine einzelne Klasse zu sein. Dies wird oft interpretiert als "Wow, DI macht meine Konstruktoren größer als Jupiter, diese Technik ist schrecklich", aber was es WIRKLICH sagt, ist, dass "Ihre Klasse eine Schiffsladung von Abhängigkeiten hat". Wenn wir das wissen, können wir es auch
Es gibt unzählige Möglichkeiten, Abhängigkeiten zu verwalten, und es ist unmöglich zu sagen, was in Ihrem Fall am besten funktioniert, ohne Ihren Code und Ihre Anwendung zu kennen.
So beantworten Sie Ihre letzten Fragen:
Ja, die Obergrenze ist "zu viele". Wie viele sind "zu viele"? "Zu viele" ist, wenn der Zusammenhalt der Klasse "zu gering" wird. Es hängt alles ab. Normalerweise, wenn Ihre Reaktion auf eine Klasse "Wow, dieses Ding hat viele Abhängigkeiten" ist, ist das zu viel.
Ich denke, diese Frage ist irreführend. Die Antwort kann ja oder nein sein. Aber es ist nicht der interessanteste Teil. Das Einfügen von Abhängigkeiten besteht darin, sie SICHTBAR zu machen. Es geht darum, eine API zu erstellen, die nicht lügt. Es geht darum, den globalen Staat zu verhindern. Es geht darum, Code testbar zu machen. Es geht darum, die Kopplung zu reduzieren.
Gut gestaltete Klassen mit vernünftig gestalteten und benannten Methoden sind besser lesbar als schlecht gestaltete. DI verbessert oder beeinträchtigt die Lesbarkeit an sich nicht wirklich, es hebt nur Ihre Designentscheidungen hervor, und wenn sie schlecht sind, stechen Ihre Augen. Dies bedeutet nicht, dass DI Ihren Code weniger lesbar gemacht hat. Es hat Ihnen nur gezeigt, dass Ihr Code bereits ein Chaos war. Sie hatten ihn gerade versteckt.
quelle
Laut Steve McConnel, Autor des allgegenwärtigen "Code Complete", gilt als Faustregel, dass mehr als 7 ein Codegeruch sind, der die Wartbarkeit beeinträchtigt. Ich persönlich denke, dass die Anzahl in den meisten Fällen niedriger ist, aber wenn Sie DI richtig ausführen, müssen Sie viele Abhängigkeiten injizieren, wenn Sie sich sehr nahe an der Kompositionswurzel befinden. Dies ist normal und wird erwartet. Dies ist einer der Gründe, warum IoC-Container eine nützliche Sache sind und die Komplexität wert sind, die sie einem Projekt hinzufügen.
Wenn Sie sich also sehr nahe am Einstiegspunkt Ihres Programms befinden, ist dies normal und akzeptabel. Wenn Sie tiefer in die Logik des Programms eintauchen, ist es wahrscheinlich ein Geruch, der angegangen werden sollte.
quelle