Ich spiele mit Abhängigkeitsinjektion, bin mir aber nicht sicher, ob ich es richtig mache. Insbesondere bin ich mir nicht sicher, wie Klassen mit injizierten Abhängigkeiten richtig erstellt werden sollen.
Angenommen, ich habe eine Klasse A, die Klasse B erstellt. Klasse B hängt von Klasse C ab und Klasse C hängt von Klasse D ab. Wer sollte für die Erstellung von Klasse D verantwortlich sein?
Es könnte Klasse A sein. In einem großen System kann Klasse A jedoch eine sehr große Anzahl von Objekten erstellen und zusammensetzen.
Eine separate Builder-Klasse, die D, C und B erstellt. A verwendet diese Builder-Klasse.
Eine andere Option.
Außerdem habe ich viel über DI-Container gelesen. Es scheint jedoch, dass es keine wesentlichen Frameworks für C ++ gibt. Wenn ich das richtig verstehe, kann DI auch ohne Container gut durchgeführt werden. Hab ich recht?
quelle
Antworten:
Du springst Schritte. Betrachten Sie eine Reihe von Konventionen, die für lose Kopplung und Ausnahmesicherheit optimiert sind. Die Regeln lauten wie folgt:
R1: Wenn A ein B enthält, erhält der Konstruktor von A ein vollständig konstruiertes B (dh nicht "Konstruktionsabhängigkeiten von B"). Wenn die Konstruktion von B ein C erfordert, erhält es in ähnlicher Weise ein C und nicht die Abhängigkeiten von C.
R2: Wenn eine vollständige Kette von Objekten erforderlich ist, um ein Objekt zu erstellen, wird die verkettete Konstruktion innerhalb einer Fabrik (Funktion oder Klasse) extrahiert / automatisiert.
Code (std :: move-Aufrufe sind der Einfachheit halber weggelassen):
In einem solchen System ist "wer D erstellt" irrelevant, denn wenn Sie make_b aufrufen, benötigen Sie ein C, kein D.
Kundencode:
Hier wird D durch den Client-Code erstellt. Wenn dieser Code mehrmals wiederholt wird, können Sie ihn natürlich in eine Funktion extrahieren (siehe R2 oben):
Es gibt eine natürliche Tendenz, die Definition von make_b zu überspringen ( R1 ignorieren ) und den Code direkt so zu schreiben:
In diesem Fall haben Sie folgende Probleme:
Sie haben monolithischen Code; Wenn Sie im Clientcode zu einer Situation kommen, in der Sie aus einem vorhandenen C ein B erstellen müssen, können Sie make_b nicht verwenden. Sie müssen entweder eine neue Factory oder die Definition von make_b und den gesamten Client-Code mit dem alten make_b schreiben.
Ihre Sicht auf Abhängigkeiten ist durcheinander, wenn Sie sich die Quelle ansehen: Wenn Sie sich nun die Quelle ansehen, denken Sie, dass Sie eine D-Instanz benötigen, obwohl Sie möglicherweise nur eine C benötigen.
Beispiel:
struct A { B make_b(C c); }
wird die Kopplung erheblich erhöhen: Jetzt muss A die Definitionen von B und C kennen (anstatt nur C). Sie haben auch Einschränkungen für jeden Clientcode, der A, B, C und D verwendet und Ihrem Projekt auferlegt wurde, da Sie einen Schritt bei der Definition einer Factory-Methode ( R1 ) übersprungen haben .TLDR: Kurz gesagt, geben Sie die äußerste Abhängigkeit nicht an eine Fabrik weiter, sondern an die nächstgelegenen. Dies macht Ihren Code robust, leicht änderbar und macht die von Ihnen gestellte Frage ("Wer erstellt D") zu einer irrelevanten Frage für die Implementierung von make_b (da make_b kein D mehr erhält, sondern eine unmittelbarere Abhängigkeit - C - und das ist injiziert als Parameter von make_b).
quelle
Es gibt ein DI-Framework für C ++ ( AFAIK befindet sich noch in der Entwicklung): Boost.DI .
Es gibt einige nützliche Kommentare zum Framework auf reddit.
quelle
Jedes Mal, wenn eine solche Frage auftaucht, weist sie auf eine zu injizierende Abhängigkeit hin . Die ganze Idee besteht darin, die Verantwortung außerhalb des Objekts zu "delegieren", bei dem es anscheinend Probleme gibt, herauszufinden, wie man damit umgeht.
Verwenden Sie Ihr Beispiel, da Klasse A nicht über ausreichende "Kenntnisse" zu verfügen scheint, um herauszufinden, wie D erstellt wird, invertieren Sie das Steuerelement und legen dies als eine für Klasse A erforderliche Abhängigkeit offen, indem Sie eine Factory benötigen , die weiß, wie man erstellt Instanzen der Klasse D.
quelle