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?
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):
struct D { int dummy; };
struct C { D d; };
struct B { C c; }
struct A { B make_b(C c) {return B{c}; };
In einem solchen System ist "wer D erstellt" irrelevant, denn wenn Sie make_b aufrufen, benötigen Sie ein C, kein D.
Kundencode:
A a; // factory instance
// construct a B instance:
D d;
C c {d};
B = a.make_b(c);
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):
B make_b_from_d(D& d) // you should probably inject A instance here as well
{
C c {d};
A a;
return a.make_b(c);
}
Es gibt eine natürliche Tendenz, die Definition von make_b zu überspringen ( R1 ignorieren ) und den Code direkt so zu schreiben:
struct D { int dummy; };
struct C { D d; };
struct B { C c; }
struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly
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:
void sub_optimal_solution(C& existent_c) {
// you cannot create a B here using existent_C, because your A::make_b
// takes a D parameter; B's construction doesn't actually need a D
// but you cannot see that at all if you just have:
// struct A { B make_b(D d); };
}
- Das Weglassen von
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).