Ich habe eine einfache Frage, und ich bin mir nicht einmal sicher, ob sie eine Antwort hat, aber versuchen wir es. Ich programmiere in C ++ und verwende Dependancy Injection, um den globalen Status zu vermeiden. Das funktioniert ganz gut und ich stoße nicht sehr oft auf unerwartetes / undefiniertes Verhalten.
Mir ist jedoch klar, dass ich mit dem Wachstum meines Projekts eine Menge Code schreibe, den ich als Boilerplate betrachte. Schlimmer noch: Die Tatsache, dass es mehr Boilerplate-Code als tatsächlichen Code gibt, macht es manchmal schwer zu verstehen.
Nichts ist besser als ein gutes Beispiel.
Ich habe eine Klasse namens TimeFactory, die Time-Objekte erstellt.
Weitere Informationen (nicht sicher, ob dies relevant ist): Zeitobjekte sind recht komplex, da die Zeit unterschiedliche Formate haben kann und die Konvertierung zwischen ihnen weder linear noch unkompliziert ist. Jede "Zeit" enthält einen Synchronizer, um Konvertierungen zu verarbeiten. Um sicherzustellen, dass sie denselben, ordnungsgemäß initialisierten Synchronizer haben, verwende ich eine TimeFactory. Die TimeFactory hat nur eine Instanz und ist anwendungsweit, sodass sie sich für Singleton qualifizieren würde. Da sie jedoch veränderlich ist, möchte ich sie nicht als Singleton definieren
In meiner App müssen viele Klassen Zeitobjekte erstellen. Manchmal sind diese Klassen tief verschachtelt.
Angenommen, ich habe eine Klasse A, die Instanzen von Klasse B usw. bis Klasse D enthält. Klasse D muss Zeitobjekte erstellen.
In meiner naiven Implementierung gebe ich die TimeFactory an den Konstruktor der Klasse A weiter, der sie an den Konstruktor der Klasse B weitergibt, und so weiter, bis Klasse D.
Stellen Sie sich vor, ich habe ein paar Klassen wie TimeFactory und ein paar Klassenhierarchien wie die oben genannte: Ich verliere die Flexibilität und Lesbarkeit, die ich für die Verwendung von Dependancy Injection empfinde.
Ich frage mich, ob meine App keinen größeren Designfehler aufweist. Oder ist dies ein notwendiges Übel bei der Verwendung von Dependancy Injection?
Was denkst du ?
quelle
Antworten:
Ihre
Time
Klasse scheint ein sehr grundlegender Datentyp zu sein, der zur "allgemeinen Infrastruktur" Ihrer Anwendung gehören sollte. DI funktioniert für solche Klassen nicht gut. Überlegen Sie, was es bedeutet, wenn eine Klasse wiestring
in jeden Teil des Codes, der Zeichenfolgen verwendet, eingefügt werden muss und Sie einestringFactory
als einzige Möglichkeit zum Erstellen neuer Zeichenfolgen verwenden müssen - die Lesbarkeit Ihres Programms würde sich um eine Größenordnung von verringern Größe.Also mein Vorschlag: benutze DI nicht für allgemeine Datentypen wie
Time
. Schreiben Sie Komponententests für dieTime
Klasse selbst, und verwenden Sie sie dann überall in Ihrem Programm, genau wie diestring
Klasse oder einevector
Klasse oder eine andere Klasse der Standardbibliothek. Verwenden Sie DI für Komponenten, die wirklich voneinander entkoppelt werden sollen.quelle
Time
und den anderen Teilen Ihres Programms zu akzeptieren . So könnte man auch eine dichte Ankopplung hinnehmenTimeFactory
. Was ich jedoch vermeiden würde, ist ein einzelnes globalesTimeFactory
Objekt mit einem Status (z. B. eine Gebietsschemainformation oder ähnliches), das böse Nebenwirkungen für Ihr Programm verursachen und allgemeine Tests und die Wiederverwendung sehr erschweren könnte. Machen Sie es entweder zustandslos oder verwenden Sie es nicht als Singleton.Time
und in welchemTimeFactory
Grad Sie "evolvability" für zukünftige Erweiterungen benötigenTimeFactory
.Was meinen Sie mit "Ich verliere all die Flexibilität und Lesbarkeit, die ich mit der Abhängigkeitsinjektion erhalten soll" - DI handelt nicht von Lesbarkeit. Es geht darum, die Abhängigkeit zwischen Objekten zu entkoppeln.
Es klingt, als hätten Sie Klasse A, die Klasse B erstellt, Klasse B, die Klasse C erstellt, und Klasse C, die Klasse D erstellt.
Was Sie haben sollten, ist Klasse B in Klasse A injiziert. Klasse C in Klasse B injiziert. Klasse D in Klasse C injiziert.
quelle
Ich bin mir nicht sicher, warum Sie Ihre Zeitfabrik nicht zu einem Singleton machen wollen. Wenn Ihre gesamte App nur eine Instanz davon enthält, handelt es sich de facto um einen Singleton.
Abgesehen davon ist es sehr gefährlich, ein veränderbares Objekt gemeinsam zu nutzen, es sei denn, es wird ordnungsgemäß durch Synchronisierungsblöcke geschützt. In diesem Fall gibt es keinen Grund, kein Singleton zu sein.
Wenn Sie eine Abhängigkeitsinjektion durchführen möchten, sollten Sie sich das Spring-Framework oder andere Frameworks für die Abhängigkeitsinjektion ansehen, mit denen Sie mithilfe einer Annotation automatisch Parameter zuweisen können
quelle
Dies zielt darauf ab, eine ergänzende Antwort auf Doc Brown zu sein und auch auf unbeantwortete Kommentare von Dinaiz zu antworten, die sich immer noch auf die Frage beziehen.
Was Sie wahrscheinlich brauchen, ist ein Framework für DI. Komplexe Hierarchien zu haben, bedeutet nicht unbedingt ein schlechtes Design. Wenn Sie jedoch eine TimeFactory-Bottom-up-Methode (von A nach D) verwenden müssen, anstatt direkt in D zu injizieren, liegt wahrscheinlich ein Fehler in der Art und Weise vor, wie Sie die Abhängigkeitsinjektion durchführen.
Ein Singleton? Nein Danke. Wenn Sie nur eine Instanz benötigen, um sie für den gesamten Anwendungskontext freizugeben (Wenn Sie einen IoC-Container für DI wie Infector ++ verwenden, müssen Sie TimeFactory nur als einzelne Instanz binden), sehen Sie sich das folgende Beispiel an (C ++ 11 übrigens, aber C ++. Möglicherweise verschieben zu C ++ 11 erhalten Sie kostenlos die Leak-Free-Anwendung):
Der Vorteil eines IoC-Containers besteht nun darin, dass Sie die Zeitfactory nicht an D übergeben müssen. Wenn Ihre Klasse "D" eine Zeitfactory benötigt, geben Sie einfach die Zeitfactory als Konstruktorparameter für die Klasse D ein.
Wie Sie sehen, spritzen Sie TimeFactory nur einmal. Wie benutze ich "A"? Ganz einfach, jede Klasse wird eingespritzt, in der Hauptleitung gebaut oder mit einer Fabrik verbunden.
Jedes Mal, wenn Sie Klasse A erstellen, werden automatisch alle Abhängigkeiten (Lazy Istantiation) und TimeFactory (D) eingefügt. Wenn Sie also nur 1 Methode aufrufen, haben Sie Ihre gesamte Hierarchie bereit (und auch komplexe Hierarchien werden auf diese Weise gelöst) Entfernen VIELES Kesselplattencodes): Sie müssen nicht "new / delete" aufrufen, und das ist sehr wichtig, da Sie die Anwendungslogik vom Klebercode trennen können.
Das ist ganz einfach, Ihre TimeFactory verfügt über eine "create" -Methode, verwenden Sie dann einfach eine andere Signatur "create (params)" und fertig. Parameter, die keine Abhängigkeiten darstellen, werden häufig auf diese Weise aufgelöst. Dadurch entfällt auch die Pflicht zum Injizieren von Elementen wie "Strings" oder "Integer", da nur eine zusätzliche Kesselplatte hinzugefügt wird.
Wer schafft wen? Der IoC-Container erstellt Bestände und Fabriken, die Fabriken erstellen den Rest (Fabriken können verschiedene Objekte mit beliebigen Parametern erstellen, sodass Sie keinen Status für Fabriken benötigen). Sie können die Fabriken weiterhin als Wrapper für den IoC-Container verwenden: Im Allgemeinen ist das Injizieren in den IoC-Container sehr schlecht und entspricht der Verwendung eines Service-Locators. Einige Leute haben das Problem gelöst, indem sie den IoC-Container mit einer Fabrik umhüllt haben (dies ist nicht unbedingt erforderlich, hat aber den Vorteil, dass die Hierarchie durch den Container gelöst wird und alle Ihre Fabriken noch einfacher zu warten sind).
Missbrauchen Sie auch nicht die Abhängigkeitsinjektion. Einfache Typen können nur Klassenmitglieder oder Variablen mit lokalem Gültigkeitsbereich sein. Dies scheint offensichtlich zu sein, aber ich habe gesehen, wie Leute "std :: vector" injizierten, nur weil es ein DI-Framework gab, das dies erlaubte. Denken Sie immer an Demeters Gesetz: "Injizieren Sie nur, was Sie wirklich brauchen, um zu injizieren"
quelle
Müssen Ihre Klassen A, B und C auch Zeitinstanzen erstellen oder nur Klasse D? Wenn es nur Klasse D ist, sollten A und B nichts über TimeFactory wissen. Erstellen Sie eine Instanz von TimeFactory in der C-Klasse und übergeben Sie sie an die Klasse D. Beachten Sie, dass mit "Instanz erstellen" nicht unbedingt gemeint ist, dass die C-Klasse für die Instanziierung der TimeFactory verantwortlich sein muss. Es kann DClassFactory von Klasse B empfangen, und DClassFactory weiß, wie eine Zeitinstanz erstellt wird.
Eine Technik, die ich auch oft verwende, wenn ich kein DI-Framework habe, besteht darin, zwei Konstruktoren bereitzustellen, einen, der eine Factory akzeptiert, und einen anderen, der eine Standard-Factory erstellt. Der zweite hat normalerweise einen geschützten / Paketzugriff und wird hauptsächlich für Komponententests verwendet.
quelle
Ich habe ein weiteres C ++ - Framework für das Einfügen von Abhängigkeiten implementiert, das kürzlich für boost vorgeschlagen wurde - https://github.com/krzysztof-jusiak/di - Bibliothek ist ohne Makros (kostenlos), nur Header, C ++ 03 / C ++ 11 / C ++ 14-Bibliothek, die typensichere, makrofreie Konstruktorabhängigkeitsinjektion zur Verfügung stellt.
quelle