Ich habe verschiedene IoC-Container (Castle.Windsor, Autofac, MEF usw.) für .Net in einer Reihe von Projekten verwendet. Ich habe festgestellt, dass sie häufig missbraucht werden und eine Reihe von schlechten Praktiken fördern.
Gibt es etablierte Praktiken für die Verwendung von IoC-Containern, insbesondere bei der Bereitstellung einer Plattform / eines Frameworks? Mein Ziel als Framework-Schreiber ist es, Code so einfach und benutzerfreundlich wie möglich zu gestalten. Ich würde lieber eine Codezeile schreiben, um ein Objekt zu konstruieren, als zehn oder sogar nur zwei.
Ein paar Code-Gerüche, die mir aufgefallen sind und für die ich keine guten Vorschläge habe:
Große Anzahl von Parametern (> 5) für Konstruktoren. Das Erstellen von Diensten ist in der Regel komplex. Alle Abhängigkeiten werden über den Konstruktor eingefügt - trotz der Tatsache, dass die Komponenten selten optional sind (außer vielleicht beim Testen).
Mangel an privaten und internen Klassen; Dies mag eine spezielle Einschränkung der Verwendung von C # und Silverlight sein, aber ich bin daran interessiert, wie es gelöst wird. Es ist schwierig zu sagen, was eine Framework-Schnittstelle ist, wenn alle Klassen öffentlich sind. Es ermöglicht mir den Zugriff auf private Teile, die ich wahrscheinlich nicht berühren sollte.
Kopplung des Objektlebenszyklus an den IoC-Container. Es ist oft schwierig, die zum Erstellen von Objekten erforderlichen Abhängigkeiten manuell zu erstellen. Der Objektlebenszyklus wird zu oft vom IoC-Framework verwaltet. Ich habe Projekte gesehen, bei denen die meisten Klassen als Singletons registriert sind. Sie haben einen Mangel an expliziter Kontrolle und sind auch gezwungen, die Interna zu verwalten (dies bezieht sich auf den obigen Punkt, alle Klassen sind öffentlich und Sie müssen sie injizieren).
Das .Net-Framework verfügt beispielsweise über viele statische Methoden. wie z. B. DateTime.UtcNow. Oft habe ich dies als Konstruktionsparameter verpackt und injiziert gesehen.
Je nach konkreter Implementierung ist mein Code nur schwer zu testen. Das Injizieren einer Abhängigkeit erschwert die Verwendung meines Codes - insbesondere, wenn die Klasse viele Parameter enthält.
Wie stelle ich sowohl eine testbare als auch eine benutzerfreundliche Schnittstelle zur Verfügung? Was sind die Best Practices?
Antworten:
Das einzige legitime Anti-Pattern für die Abhängigkeitsinjektion, das mir bekannt ist, ist das Service Locator- Pattern, bei dem es sich um ein Anti-Pattern handelt, wenn ein DI-Framework dafür verwendet wird.
Alle anderen so genannten DI-Anti-Patterns, von denen ich hier oder anderswo gehört habe, sind nur etwas spezifischere Fälle von allgemeinen OO / Software-Design-Anti-Patterns. Zum Beispiel:
Die Überinjektion von Konstruktoren ist eine Verletzung des Single-Responsibility-Prinzips . Zu viele Konstruktorargumente weisen auf zu viele Abhängigkeiten hin. Zu viele Abhängigkeiten deuten darauf hin, dass die Klasse versucht, zu viel zu tun. Normalerweise korreliert dieser Fehler mit anderen Code-Gerüchen, wie ungewöhnlich langen oder mehrdeutigen Klassennamen ("Manager"). Statische Analysewerkzeuge können leicht übermäßige afferente / efferente Kopplung erkennen.
Das Injizieren von Daten ist im Gegensatz zum Verhalten ein Subtyp des poltergeistischen Anti-Musters, wobei der Geist in diesem Fall der Container ist. Wenn eine Klasse das aktuelle Datum und die aktuelle Uhrzeit kennen muss, injizieren Sie keine
DateTime
Daten. Stattdessen injizieren Sie eine Abstraktion über die Systemuhr (normalerweise nenne ich meineISystemClock
, obwohl ich denke, dass es eine allgemeinere im SystemWrappers- Projekt gibt). Dies gilt nicht nur für DI; Dies ist für die Testbarkeit unbedingt erforderlich, damit Sie zeitlich veränderliche Funktionen testen können, ohne darauf warten zu müssen.Jeden Lebenszyklus als Singleton zu deklarieren, ist für mich ein perfektes Beispiel für Frachtkultprogrammierung und in geringerem Maße die umgangssprachliche Bezeichnung " Objekt-Senkgrube ". Ich habe mehr Missbrauch von Singletons gesehen, als mir einfällt, und nur sehr wenig davon betrifft DI.
Ein weiterer häufiger Fehler sind implementierungsspezifische Schnittstellentypen (mit seltsamen Namen wie
IOracleRepository
), die nur ausgeführt werden, um sie im Container registrieren zu können. Dies ist an und für sich ein Verstoß gegen das Prinzip der Inversion von Abhängigkeiten (nur weil es sich um eine Schnittstelle handelt, bedeutet dies nicht, dass es wirklich abstrakt ist) und beinhaltet häufig auch ein Aufblähen der Schnittstelle, das das Prinzip der Schnittstellentrennung verletzt .Der letzte Fehler, den ich normalerweise sehe, ist die "optionale Abhängigkeit", die sie in NerdDinner gemacht haben . Mit anderen Worten, es gibt einen Konstruktor, der die Abhängigkeitsinjektion akzeptiert, aber auch einen anderen Konstruktor, der eine "Standard" -Implementierung verwendet. Dies auch verletzt die DIP und neigt zu führen LSP als Entwickler Verletzungen als auch, im Laufe der Zeit beginnen Annahmen um die Default - Implementierung zu machen und / oder starten neu-ing up Instanzen des Standardkonstruktors verwenden.
Wie das alte Sprichwort sagt, können Sie FORTRAN in jeder Sprache schreiben . Dependency Injection ist kein Allheilmittel , die ihr Abhängigkeitsmanagement verhindern werden die Entwickler von vermasseln, aber es tut verhindert eine Reihe von gemeinsamen Fehlern / anti-Muster:
...und so weiter.
Natürlich möchten Sie kein Framework entwerfen, das von einer bestimmten IoC-Container- Implementierung wie Unity oder AutoFac abhängt. Dies verstößt erneut gegen das DIP. Wenn Sie jedoch einmal darüber nachdenken, müssen Sie bereits einige Entwurfsfehler gemacht haben, da die Abhängigkeitsinjektion eine allgemeine Abhängigkeitsverwaltungstechnik ist und nicht an das Konzept eines IoC-Containers gebunden ist.
Jeder kann einen Abhängigkeitsbaum erstellen. Vielleicht ist es ein IoC-Container, vielleicht ist es ein Unit-Test mit einer Reihe von Mocks, vielleicht ist es ein Testtreiber, der Dummy-Daten liefert. Ihr Framework sollte sich nicht darum kümmern, und die meisten Frameworks, die ich gesehen habe, interessieren sich nicht dafür, nutzen jedoch die Abhängigkeitsinjektion in hohem Maße, damit sie problemlos in den IoC-Container der Wahl des Endbenutzers integriert werden kann.
DI ist keine Raketenwissenschaft. Versuchen Sie nur zu vermeiden
new
und es seistatic
denn, es gibt einen zwingenden Grund, sie zu verwenden, z. B. eine Utility - Methode, die keine externen Abhängigkeiten aufweist, oder eine Utility - Klasse, die möglicherweise keinen Zweck außerhalb des Frameworks hat (Interop - Wrapper und Dictionary - Schlüssel sind häufige Beispiele dafür) Dies).Viele Probleme mit IoC-Frameworks treten auf, wenn Entwickler erst lernen, wie sie verwendet werden, und anstatt die Art und Weise, wie sie mit Abhängigkeiten und Abstraktionen umgehen, an das IoC-Modell anzupassen, versuchen Sie stattdessen, den IoC-Container so zu manipulieren, dass er den Erwartungen der Entwickler entspricht alter Codierungsstil, der häufig eine hohe Kopplung und eine geringe Kohäsion beinhaltet. Schlechter Code ist schlechter Code, unabhängig davon, ob DI-Techniken verwendet werden oder nicht.
quelle