it-swarm.com.de

Abhängigkeitsspritze ; bewährte Methoden zur Reduzierung des Boilerplate-Codes

Ich habe eine einfache Frage und bin mir nicht einmal sicher, ob sie eine Antwort hat, aber versuchen wir es. Ich codiere in C++ und verwende die Abhängigkeitsinjektion, um einen globalen Status zu vermeiden. Dies funktioniert ganz gut und ich laufe nicht sehr oft in unerwarteten/undefinierten Verhaltensweisen.

Mir ist jedoch klar, dass ich mit dem Wachstum meines Projekts viel 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, also lass uns gehen:

Ich habe eine Klasse namens TimeFactory, die Zeitobjekte erstellt.

Für weitere Details (nicht sicher, ob es 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 Synchronisierer für die Konvertierung von und Um sicherzustellen, dass sie denselben, ordnungsgemäß initialisierten Synchronisierer haben, verwende ich eine TimeFactory. Die TimeFactory hat nur eine Instanz und ist anwendungsweit, daher würde sie sich für Singleton qualifizieren, aber da sie veränderlich ist, möchte ich sie nicht zu einer machen Singleton

In meiner App müssen viele Klassen Zeitobjekte erstellen. Manchmal sind diese Klassen tief verschachtelt.

Angenommen, ich habe eine Klasse A, die Instanzen der Klasse B usw. bis zur Klasse D enthält. Klasse D muss Zeitobjekte erstellen.

In meiner naiven Implementierung übergebe ich die TimeFactory an den Konstruktor der Klasse A, der sie an den Konstruktor der Klasse B usw. weitergibt, 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 der Abhängigkeitsinjektion benötige.

Ich frage mich, ob meine App keinen größeren Designfehler aufweist ... Oder ist dies ein notwendiges Übel bei der Verwendung der Abhängigkeitsinjektion?

Was denken Sie ?

25
Dinaiz

In meiner App müssen viele Klassen Zeitobjekte erstellen

Scheint, dass Ihre Time -Klasse ein sehr grundlegender Datentyp ist, 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 wie string in jeden Teil des Codes eingefügt werden muss, der Zeichenfolgen verwendet, und Sie müssten ein stringFactory als einzige Möglichkeit zum Erstellen neuer Zeichenfolgen verwenden - Die Lesbarkeit Ihres Programms würde sich um eine Größenordnung verringern.

Also mein Vorschlag: Verwenden Sie DI nicht für allgemeine Datentypen wie Time. Schreiben Sie Komponententests für die Klasse Time selbst, und verwenden Sie sie anschließend überall in Ihrem Programm, genau wie die Klasse string oder eine Klasse vector oder eine andere Klasse der Standardbibliothek. Verwenden Sie DI für Komponenten, die wirklich voneinander entkoppelt werden sollten.

23
Doc Brown

Was meinst du mit "Ich verliere all die Flexibilität und Lesbarkeit, die ich für die Verwendung der Abhängigkeitsinjektion bekomme" - DI geht es nicht um Lesbarkeit. Es geht darum, die Abhängigkeit zwischen Objekten zu entkoppeln.

Es hört sich so an, als ob Klasse A Klasse B, Klasse B Klasse C und Klasse C 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.

4
C0deAttack

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änderliches 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 Spring oder andere Frameworks für die Abhängigkeitsinjektion ansehen, mit denen Sie Parameter mithilfe einer Anmerkung automatisch zuweisen können

3
molyss

Dies soll eine ergänzende Antwort auf Doc Brown sein und auch auf unbeantwortete Kommentare von Dinaiz antworten, die sich noch auf die Frage beziehen.

Was Sie wahrscheinlich brauchen, ist ein Framework für DI. Komplexe Hierarchien zu haben, bedeutet nicht unbedingt schlechtes Design. Wenn Sie jedoch eine TimeFactory von unten nach oben (von A nach D) injizieren müssen, anstatt direkt nach D zu injizieren, stimmt wahrscheinlich etwas nicht mit der Art und Weise, wie Sie die Abhängigkeitsinjektion durchführen.

Ein Singleton? Nein Danke. Wenn Sie nur eine Istance benötigen, machen Sie sie für Ihren Anwendungskontext freigegeben (für die Verwendung eines IoC-Containers für DI wie Infector ++ muss TimeFactory nur als einzelne Istance gebunden werden), hier das Beispiel (C++ 11 übrigens, aber so C++. Vielleicht verschieben zu C++ 11 bereits? Sie erhalten eine leckfreie Anwendung kostenlos):

Infector::Container ioc; //your app's context

ioc.bindSingleAsNothing<TimeFactory>(); //declare TimeFactory to be shared
ioc.wire<TimeFactory>(); //wire its constructor 

// if you want to be sure TimeFactory is created at startup just request it
// (else it will be created lazily only when needed)
auto myTimeFactory = ioc.buildSingle<TimeFactory>();

Der gute Punkt eines IoC-Containers ist nun, dass Sie die Zeitfabrik nicht an D übergeben müssen. Wenn Ihre Klasse "D" eine Zeitfabrik benötigt, geben Sie einfach die Zeitfabrik als Konstruktorparameter für die Klasse D ein.

ioc.bindAsNothing<A>(); //declare class A
ioc.bindAsNothing<B>(); //declare class B
ioc.bindAsNothing<D>(); //declare class D

//constructors setup
ioc.wire<D, TimeFactory>(); //time factory injected to class D
ioc.wire<B, D>(); //class D injected to class B
ioc.wire<A, B>(); //class B injected to class A

wie Sie sehen, injizieren Sie TimeFactory nur einmal. Wie benutzt man "A"? Sehr einfach, jede Klasse wird eingespritzt, im Hauptgebäude gebaut oder mit einer Fabrik ausgestattet.

auto myA1 = ioc.build<A>(); //A is not "single" so many different istances
auto myA2 = ioc.build<A>(); //can live at same time

jedes Mal, wenn Sie Klasse A erstellen, wird diese automatisch (Lazy Istantiation) mit allen Abhängigkeiten bis D injiziert, und D wird mit TimeFactory injiziert. Wenn Sie also nur eine Methode aufrufen, haben Sie Ihre vollständige Hierarchie parat (und selbst komplexe Hierarchien werden auf diese Weise gelöst VIELEN Kesselplattencode entfernen): Sie müssen nicht "neu/löschen" aufrufen, und das ist sehr wichtig, da Sie die Anwendungslogik vom Klebercode trennen können.

D kann Zeitobjekte mit Informationen erstellen, die nur D haben kann

Das ist einfach, Ihre TimeFactory hat eine "create" -Methode, dann verwenden Sie einfach eine andere Signatur "create (params)" und Sie sind fertig. Parameter, die keine Abhängigkeiten sind, werden häufig auf diese Weise aufgelöst. Dies beseitigt auch die Pflicht, Dinge wie "Strings" oder "Ganzzahlen" einzuspritzen, da dadurch nur eine zusätzliche Kesselplatte hinzugefügt wird.

Wer schafft wen? Der IoC-Container erstellt Istanzen und Fabriken, die Fabriken erstellen den Rest (Fabriken können verschiedene Objekte mit beliebigen Parametern erstellen, sodass Sie für Fabriken keinen Status benötigen). Sie können die Fabriken weiterhin als Wrapper für den IoC-Container verwenden: Im Allgemeinen ist das Injizieren des IoC-Containers sehr schlecht und entspricht der Verwendung eines Service-Locators. Einige Leute haben das Problem gelöst, indem sie den IoC-Container mit einer Fabrik umwickelt haben (dies ist nicht unbedingt erforderlich, hat aber den Vorteil, dass die Hierarchie vom Container gelöst wird und alle Ihre Fabriken noch einfacher zu warten sind).

//factory method
std::unique_ptr<myType> create(params){
    auto istance = ioc->build<myType>(); //this code's agnostic to "myType" hierarchy
    istance->setParams(params); //the customization you needed
    return std::move(istance); 
}

Missbrauche auch nicht die Abhängigkeitsinjektion, einfache Typen können nur Klassenmitglieder oder Variablen mit lokalem Gültigkeitsbereich sein. Dies scheint offensichtlich, aber ich sah Leute, die "std :: vector" injizierten, nur weil es ein DI-Framework gab, das dies erlaubte. Denken Sie immer an Demeters Gesetz: "Injizieren Sie nur das, was Sie wirklich injizieren müssen"

1
CoffeDeveloper

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 innerhalb der C-Klasse und übergeben Sie sie an 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 Standardfactory erstellt. Der zweite hat normalerweise einen geschützten/Paketzugriff und wird hauptsächlich für Komponententests verwendet.

0
rmaruszewski