Es gibt viele Gründe, warum Globale in OOP böse sind .
Wenn die Anzahl oder Größe der Objekte, die gemeinsam genutzt werden müssen, zu groß ist, um effizient in Funktionsparametern weitergegeben zu werden, empfiehlt normalerweise jeder die Abhängigkeitsinjektion anstelle eines globalen Objekts.
In dem Fall, in dem fast jeder eine bestimmte Datenstruktur kennen muss, warum ist Dependency Injection besser als ein globales Objekt?
Beispiel (ein vereinfachtes Beispiel , um den Punkt allgemein darzustellen, ohne zu tief in eine bestimmte Anwendung einzutauchen)
Es gibt eine Reihe von virtuellen Fahrzeugen mit einer Vielzahl von Eigenschaften und Zuständen, von Typ, Name, Farbe bis zu Geschwindigkeit, Position usw. Eine Reihe von Benutzern kann sie fernsteuern, und eine Vielzahl von Ereignissen (beides initiiert und automatisch) können viele ihrer Zustände oder Eigenschaften ändern.
Die naive Lösung wäre, einfach einen globalen Container daraus zu machen
vector<Vehicle> vehicles;
die von überall zugegriffen werden kann.
Die OOP-freundlichere Lösung wäre, dass der Container Mitglied der Klasse ist, die die Hauptereignisschleife behandelt, und in seinem Konstruktor instanziiert wird. Jede Klasse, die es benötigt und Mitglied des Hauptthreads ist, erhält über einen Zeiger in ihrem Konstruktor Zugriff auf den Container. Wenn beispielsweise eine externe Nachricht über eine Netzwerkverbindung eingeht, übernimmt eine Klasse (eine für jede Verbindung), die das Parsing ausführt, und der Parser hat über einen Zeiger oder eine Referenz Zugriff auf den Container. Wenn die analysierte Nachricht zu einer Änderung in einem Element des Containers führt oder einige Daten benötigt, um eine Aktion auszuführen, kann sie verarbeitet werden, ohne dass Tausende von Variablen durch Signale und Slots geworfen werden müssen (oder schlimmer noch, Speichern Sie sie im Parser, damit sie später von demjenigen abgerufen werden können, der den Parser aufgerufen hat. Natürlich sind alle Klassen, die über Dependency Injection Zugriff auf den Container erhalten, Teil desselben Threads. Verschiedene Threads greifen nicht direkt darauf zu, sondern erledigen ihre Aufgabe und senden dann Signale an den Haupt-Thread. Die Slots im Haupt-Thread aktualisieren den Container.
Wenn jedoch die Mehrheit der Klassen Zugriff auf den Container erhält, was unterscheidet ihn dann wirklich von einem globalen? Wenn so viele Klassen die Daten im Container benötigen, ist die "Abhängigkeitsinjektionsmethode" dann nicht nur eine verschleierte globale Methode?
Eine Antwort wäre Thread-Sicherheit: Auch wenn ich darauf achte, den globalen Container nicht zu missbrauchen, wird möglicherweise ein anderer Entwickler in Zukunft unter dem Druck einer kurzen Frist den globalen Container in einem anderen Thread verwenden, ohne sich um alles zu kümmern die Kollisionsfälle. Selbst im Fall der Abhängigkeitsinjektion kann man jedoch einen Zeiger auf jemanden geben, der in einem anderen Thread ausgeführt wird, was zu denselben Problemen führt.
Antworten:
Abhängigkeitsinjektion ist die beste Sache seit geschnittenem Brot , während globale Objekte seit Jahrzehnten dafür bekannt sind, die Quelle allen Übels zu sein , daher ist dies eine ziemlich interessante Frage.
Der Punkt der Abhängigkeitsinjektion besteht nicht einfach darin, sicherzustellen, dass jeder Akteur, der eine Ressource benötigt, diese haben kann, denn wenn Sie alle Ressourcen globalisieren, hat natürlich jeder Akteur Zugriff auf jede Ressource, das Problem ist gelöst, oder?
Der Punkt der Abhängigkeitsinjektion ist:
Die Tatsache, dass in Ihrer speziellen Konfiguration alle Akteure Zugriff auf dieselbe Ressourceninstanz benötigen, ist irrelevant. Vertrauen Sie mir, eines Tages müssen Sie die Dinge neu konfigurieren, damit die Akteure Zugriff auf verschiedene Instanzen der Ressource haben, und dann werden Sie feststellen, dass Sie sich selbst in eine Ecke gemalt haben. Einige Antworten haben bereits auf eine solche Konfiguration hingewiesen: Testen .
Ein weiteres Beispiel: Angenommen, Sie teilen Ihre Anwendung in Client-Server auf. Alle Akteure auf dem Client verwenden denselben Satz zentraler Ressourcen auf dem Client, und alle Akteure auf dem Server verwenden denselben Satz zentraler Ressourcen auf dem Server. Angenommen, Sie möchten eines Tages eine "eigenständige" Version Ihrer Client-Server-Anwendung erstellen, bei der sowohl der Client als auch der Server in einer einzigen ausführbaren Datei zusammengefasst sind und auf derselben virtuellen Maschine ausgeführt werden. (Oder Laufzeitumgebung, abhängig von der Sprache Ihrer Wahl.)
Wenn Sie die Abhängigkeitsinjektion verwenden, können Sie auf einfache Weise sicherstellen, dass alle Client-Akteure die Client-Ressourceninstanzen erhalten, mit denen sie arbeiten können, während alle Server-Akteure die Server-Ressourceninstanzen erhalten.
Wenn Sie keine Abhängigkeitsinjektion verwenden, haben Sie kein Glück, da in einer virtuellen Maschine nur eine globale Instanz jeder Ressource vorhanden sein kann.
Dann muss man bedenken: Brauchen wirklich alle Akteure Zugang zu dieser Ressource? Ja wirklich?
Möglicherweise haben Sie den Fehler begangen, diese Ressource in ein Gott-Objekt zu verwandeln (so muss natürlich jeder darauf zugreifen können), oder Sie überschätzen die Anzahl der Akteure in Ihrem Projekt, die tatsächlich Zugriff auf diese Ressource benötigen, grob .
Mit Globals hat jede einzelne Codezeile in Ihrer gesamten Anwendung Zugriff auf jede einzelne globale Ressource. Bei der Abhängigkeitsinjektion ist jede Ressourceninstanz nur für diejenigen Akteure sichtbar, die sie tatsächlich benötigen. Wenn die beiden identisch sind (die Akteure, die eine bestimmte Ressource benötigen, machen 100% der Quellcodezeilen in Ihrem Projekt aus), müssen Sie einen Fehler in Ihrem Entwurf gemacht haben. Also entweder
Zerlegen Sie diese große, große, große Gott-Ressource in kleinere Subressourcen, sodass verschiedene Schauspieler Zugriff auf verschiedene Teile davon benötigen, aber selten benötigt ein Schauspieler alle Teile davon oder
Überarbeiten Sie Ihre Akteure so, dass sie nur die Teilmengen des Problems als Parameter akzeptieren, an denen sie arbeiten müssen, damit sie nicht ständig auf eine große, große, zentrale Ressource zurückgreifen müssen.
quelle
Das ist eine zweifelhafte Behauptung. Der Link , den Sie als Beweismittel verwenden bezieht sich auf Zustand - wandelbar Globals. Sie sind entschieden böse. Read only Globals sind nur Konstanten. Konstanten sind relativ gesund. Ich meine, Sie werden nicht in all Ihren Klassen den Wert von pi angeben, oder?
Nein, das tun sie nicht.
Wenn Ihre Funktionen / Klassen zu viele Abhängigkeiten aufweisen, empfehlen die Benutzer, anzuhalten und zu prüfen, warum Ihr Design so schlecht ist. Eine Schiffsladung von Abhängigkeiten ist ein Zeichen dafür, dass Ihre Klasse / Funktion wahrscheinlich zu viele Aufgaben ausführt und / oder dass Sie keine ausreichende Abstraktion haben.
Dies ist ein Design-Albtraum. Sie haben eine Reihe von Dingen, die ohne jede Validierung und ohne jede Einschränkung der Parallelität verwendet / missbraucht werden können - es ist nur eine vollständige Kopplung.
Ja, mit Kopplung an gemeinsamen Daten überall ist nicht allzu verschieden von einem globalen, und als solche immer noch schlecht.
Der Vorteil ist, dass Sie die Verbraucher von der globalen Variablen selbst entkoppeln. Dies bietet Ihnen einige Flexibilität, wie die Instanz erstellt wird. Es zwingt Sie auch nicht dazu , nur eine Instanz zu haben. Sie können verschiedene Produkte erstellen, die an die verschiedenen Verbraucher weitergegeben werden. Sie können dann auch verschiedene Implementierungen Ihrer Benutzeroberfläche pro Benutzer erstellen, falls dies erforderlich sein sollte.
Und aufgrund der Veränderungen, die unvermeidlich mit den sich wandelnden Anforderungen von Software einhergehen, ist ein derart grundlegendes Maß an Flexibilität von entscheidender Bedeutung .
quelle
foo
s"?Es gibt drei Hauptgründe, die Sie berücksichtigen sollten.
Um es zusammenzufassen: DI ist kein Ziel, es ist nur ein Werkzeug, mit dem es möglich ist, ein Ziel zu erreichen. Wenn Sie es missbrauchen (wie es in Ihrem Beispiel der Fall ist), werden Sie dem Ziel nicht näher kommen. Es gibt keine magische Eigenschaft von DI, die ein schlechtes Design gut macht.
quelle
Es geht wirklich um Testbarkeit - normalerweise ist ein globales Objekt sehr wartbar und leicht zu lesen / verstehen (wenn Sie es erst einmal kennen, natürlich), aber es ist ein fester Bestandteil, und wenn Sie Ihre Klassen in isolierte Tests aufteilen, stellen Sie fest, dass Ihre Das globale Objekt steckt fest und sagt "Was ist mit mir?". Daher erfordern Ihre isolierten Tests, dass das globale Objekt in ihnen enthalten ist. Es hilft nicht, dass die meisten Test-Frameworks dieses Szenario nicht einfach unterstützen.
Also ja, es ist immer noch global. Der grundlegende Grund dafür hat sich nicht geändert, nur die Art und Weise, wie darauf zugegriffen wird.
Was die Initialisierung betrifft, ist dies immer noch ein Problem. Sie müssen die Konfiguration immer noch so einstellen, dass Ihre Tests ausgeführt werden können, sodass Sie dort nichts gewinnen. Ich nehme an, Sie könnten ein großes abhängiges Objekt in viele kleinere Objekte aufteilen und das entsprechende an die Klassen übergeben, die es benötigen, aber Sie werden wahrscheinlich noch komplexer, wenn Sie die Vorteile überwiegen.
Unabhängig davon muss getestet werden, wie die Codebasis aufgebaut ist (ähnliche Probleme treten bei Elementen wie statischen Klassen auf, z. B. aktuelle Datums- und Uhrzeitangaben).
Persönlich ziehe ich es vor, alle meine globalen Daten in ein globales Objekt (oder mehrere) zu packen, aber Zugriffsmethoden bereitzustellen, um die Bits zu erhalten, die meine Klassen benötigen. Dann kann ich diejenigen verspotten, um die Daten zurückzugeben, die ich beim Testen ohne die zusätzliche Komplexität von DI haben möchte.
quelle
Zusätzlich zu den Antworten, die Sie bereits haben,
Eines der Merkmale der OOP-Programmierung ist, nur die Eigenschaften beizubehalten, die Sie wirklich für ein bestimmtes Objekt benötigen.
WENN Ihr Objekt zu viele Eigenschaften hat, sollten Sie Ihr Objekt weiter in Unterobjekte aufteilen.
Bearbeiten
Bereits erwähnt, warum globale Objekte schlecht sind, möchte ich ein Beispiel hinzufügen.
Sie müssen Unit-Tests mit dem GUI-Code eines Windows-Formulars durchführen. Wenn Sie global verwenden, müssen Sie alle Schaltflächen und Ereignisse erstellen, um beispielsweise einen richtigen Testfall zu erstellen.
quelle