Es fällt mir schwer, nach Ressourcen zu suchen, warum ich die Abhängigkeitsinjektion verwenden sollte . Die meisten der angezeigten Ressourcen erklären, dass nur eine Instanz eines Objekts an eine andere Instanz eines Objekts übergeben wird. Aber warum? Ist dies nur für eine sauberere Architektur / Code oder wirkt sich dies auf die Leistung insgesamt aus?
Warum sollte ich folgendes tun?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Anstelle der folgenden?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
deactivateProfile
legt mir jedoch nahe, dass das SetzenisActive
von false ohne Rücksicht auf den vorherigen Status hier der richtige Ansatz ist. Das Aufrufen der Methode inhärent bedeutet, dass Sie sie als inaktiv festlegen und nicht ihren aktuellen (in) aktiven Status erhalten möchten.Antworten:
Der Vorteil ist, dass ohne Abhängigkeitsinjektion Ihre Profilklasse
Aber mit Abhängigkeitsinjektion
Dies mag in diesem speziellen Fall irrelevant erscheinen (oder sogar sein), aber stellen Sie sich vor, dass es sich nicht um ein Settings-Objekt handelt, sondern um ein DataStore-Objekt, das möglicherweise unterschiedliche Implementierungen aufweist. Eines speichert Daten in Dateien, das andere speichert sie in eine Datenbank. Und für automatisierte Tests möchten Sie auch eine Scheinimplementierung. Jetzt möchten Sie wirklich nicht, dass die Profile-Klasse fest codiert, welche sie verwendet - und, was noch wichtiger ist, Sie möchten wirklich, dass die Profile-Klasse nichts über Dateisystempfade, DB-Verbindungen und Kennwörter weiß, also über die Erstellung von DataStore-Objekten muss woanders passieren.
quelle
This may seem (or even be) irrelevant in this particular case
Ich denke, es ist in der Tat sehr relevant. Wie würdest du die Einstellungen bekommen? Viele Systeme, die ich gesehen habe, verfügen über einen fest codierten Standardeinstellungssatz und eine öffentlich zugängliche Konfiguration. Daher müssen Sie beide laden und einige Werte mit den öffentlichen Einstellungen überschreiben. Möglicherweise benötigen Sie sogar mehrere Standardquellen. Vielleicht bekommen Sie sogar einige von der Festplatte, andere von der DB. Die gesamte Logik für das Abrufen von Einstellungen kann und ist daher häufig nicht trivial - auf keinen Fall sollte oder würde sich etwas für verbrauchenden Code interessieren.$setting = new Setting();
horrend ineffizient wäre. Injektion und Objektinstanziierung erfolgen einmal.Die Abhängigkeitsinjektion erleichtert das Testen Ihres Codes.
Das habe ich aus erster Hand gelernt, als ich einen schwer zu findenden Fehler in der PayPal-Integration von Magento beheben musste.
Ein Problem trat auf, wenn PayPal Magento über eine fehlgeschlagene Zahlung informierte: Magento konnte den Fehler nicht ordnungsgemäß registrieren.
Das Testen einer potenziellen Korrektur "manuell" wäre sehr mühsam: Sie müssten irgendwie eine PayPal-Benachrichtigung "Fehlgeschlagen" auslösen. Sie müssten einen E-Check einreichen, ihn abbrechen und warten, bis ein Fehler auftritt. Das bedeutet mehr als 3 Tage, um eine Änderung des Ein-Zeichen-Codes zu testen!
Glücklicherweise scheint es, dass die Magento-Kernentwickler, die diese Funktion entwickelt haben, Tests im Auge hatten und ein Abhängigkeitsinjektionsmuster verwendeten, um es trivial zu machen. Dies ermöglicht es uns, unsere Arbeit mit einem einfachen Testfall wie diesem zu verifizieren:
Ich bin mir sicher, dass das DI-Muster viele andere Vorteile bietet, aber eine verbesserte Testbarkeit ist der größte Vorteil für mich .
Wenn Sie nach einer Lösung für dieses Problem suchen, lesen Sie das GitHub-Repo hier: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
quelle
Warum (was ist überhaupt das Problem)?
Die beste Mnemonik, die ich dafür gefunden habe, ist " new is glue ": Jedes Mal, wenn Sie
new
in Ihrem Code verwenden, ist dieser Code an diese spezielle Implementierung gebunden . Wenn Sie in Konstruktoren wiederholt new verwenden, erstellen Sie eine Kette spezifischer Implementierungen . Und weil Sie eine Instanz einer Klasse nicht "haben" können, ohne sie zu konstruieren, können Sie diese Kette nicht trennen.Stellen Sie sich zum Beispiel vor, Sie schreiben ein Rennwagen-Videospiel. Sie haben mit einer Klasse begonnen
Game
, die eine erstelltRaceTrack
, die 8 erstelltCars
, die jeweils eine erstelltMotor
. Wenn Sie jetzt 4 zusätzlicheCars
mit einer anderen Beschleunigung wünschen , müssen Sie jede erwähnte Klasse ändern , außer vielleichtGame
.Besserer Code
Ja .
In dieser Situation mag es jedoch sehr wohl weniger klar erscheinen , da es eher ein Beispiel dafür ist, wie man es macht . Der tatsächliche Vorteil zeigt sich nur, wenn mehrere Klassen beteiligt sind, und ist schwieriger zu demonstrieren. Stellen Sie sich jedoch vor, Sie hätten DI im vorherigen Beispiel verwendet. Der Code, der all diese Dinge erstellt, könnte ungefähr so aussehen:
Das Hinzufügen dieser 4 verschiedenen Autos kann nun durch Hinzufügen dieser Zeilen erfolgen:
RaceTrack
,Game
,Car
oderMotor
waren notwendig - was bedeutet , dass wir 100% sicher sein, dass wir keine neuen Fehler dort nicht eingeführt haben!Leistungsaspekte
Nein . Aber um ganz ehrlich zu sein, könnte es sein.
Aber selbst in diesem Fall ist es eine so lächerlich kleine Menge, dass Sie sich nicht darum kümmern müssen. Wenn zu einem bestimmten Zeitpunkt in der Zukunft, müssen Sie Code für ein Tamagotchi mit dem Äquivalent von 5 MHz CPU und 2 MB RAM schreiben, dann vielleicht Sie vielleicht haben darüber kümmern.
In 99,999% * der Fälle wird die Leistung verbessert, da Sie weniger Zeit für die Behebung von Fehlern und mehr Zeit für die Verbesserung Ihrer ressourcenintensiven Algorithmen aufgewendet haben.
* komplett erfundene Nummer
Info hinzugefügt: "fest codiert"
Machen Sie keinen Fehler, dies ist immer noch sehr "hartcodiert" - die Zahlen werden direkt in den Code geschrieben. Nicht fest codiert würde bedeuten, dass diese Werte in einer Textdatei gespeichert werden - z. B. im JSON-Format - und dann aus dieser Datei gelesen werden.
Dazu müssen Sie Code hinzufügen, um eine Datei zu lesen und anschließend JSON zu analysieren. Wenn Sie das Beispiel noch einmal betrachten; In der Nicht-DI-Version muss a
Car
oder aMotor
jetzt eine Datei lesen. Das klingt nicht so, als ob es zu viel Sinn macht.In der DI-Version würden Sie es dem Code hinzufügen, der das Spiel einrichtet.
quelle
Ich war immer verwirrt von der Abhängigkeitsspritze. Es schien nur in Java-Sphären zu existieren, aber diese Sphären sprachen mit großer Ehrfurcht darüber. Es war eines der großen Muster, die Ordnung ins Chaos bringen sollen. Die Beispiele waren jedoch immer verworren und künstlich und stellten ein unproblematisches Problem dar und machten sich dann daran, es zu lösen, indem sie den Code komplizierter machten.
Es machte mehr Sinn, als mir ein Python-Kollege diese Weisheit mitteilte: Es geht nur darum, Argumente an Funktionen weiterzugeben. Es ist überhaupt kein Muster; eher wie eine Erinnerung, dass Sie um etwas als Argument bitten können , selbst wenn Sie möglicherweise selbst einen angemessenen Wert geliefert haben könnten.
Ihre Frage ist also ungefähr gleichbedeutend mit "Warum sollte meine Funktion Argumente verwenden?" und hat viele der gleichen Antworten, nämlich: den Anrufer Entscheidungen treffen zu lassen .
Dies ist zwar mit Kosten, natürlich, denn jetzt bist du zwingt den Anrufer zu machen einige Entscheidung (es sei denn Sie das Argument optional zu machen), und die Schnittstelle ist etwas komplexer. Im Gegenzug gewinnen Sie Flexibilität.
Also: Gibt es einen guten Grund, diesen speziellen
Setting
Typ / Wert zu verwenden? Gibt es einen guten Grund, warum ein Aufruf von Code einen anderenSetting
Typ / Wert haben soll? (Denken Sie daran, Tests sind Code !)quelle
Das Beispiel, das Sie angeben, ist keine Abhängigkeitsinjektion im klassischen Sinne. Unter Abhängigkeitsinjektion versteht man normalerweise das Übergeben von Objekten in einem Konstruktor oder die Verwendung der "Setter-Injektion" unmittelbar nach dem Erstellen des Objekts, um einen Wert für ein Feld in einem neu erstellten Objekt festzulegen.
Ihr Beispiel übergibt ein Objekt als Argument an eine Instanzmethode. Diese Instanzmethode ändert dann ein Feld für dieses Objekt. Abhängigkeitsspritze? Nein. Kapselung aufbrechen und Daten verstecken? Absolut!
Nun, wenn der Code so war:
Dann würde ich sagen, dass Sie Abhängigkeitsinjektion verwenden. Der bemerkenswerte Unterschied ist ein
Settings
Objekt, das an den Konstruktor übergeben wird, oder einProfile
Objekt.Dies ist hilfreich, wenn das Settings-Objekt teuer oder komplex zu konstruieren ist oder Settings eine Schnittstelle oder eine abstrakte Klasse ist, in der mehrere konkrete Implementierungen vorhanden sind, um das Laufzeitverhalten zu ändern.
Da Sie direkt auf ein Feld im Settings-Objekt zugreifen, anstatt eine Methode aufzurufen, können Sie den Polymorphismus nicht nutzen, der einer der Vorteile der Abhängigkeitsinjektion ist.
Es sieht so aus, als wären die Einstellungen für ein Profil für dieses Profil spezifisch. In diesem Fall würde ich einen der folgenden Schritte ausführen:
Instanziieren Sie das Settings-Objekt im Profile-Konstruktor
Übergeben Sie das Settings-Objekt im Konstruktor und kopieren Sie die einzelnen Felder, die für das Profil gelten
Ehrlich gesagt ist das Übergeben des Settings-Objekts an
deactivateProfile
ein internes Feld des Settings-Objekts und das anschließende Ändern dieses Felds ein Codegeruch. Das Settings-Objekt sollte das einzige sein, das seine internen Felder ändert.quelle
The example you give is not dependency injection in the classical sense.
- Es ist egal. OO-Leute haben Objekte im Gehirn, aber Sie geben immer noch eine Abhängigkeit von etwas ab.Ich weiß, dass ich zu spät zu dieser Party komme, aber ich glaube, ein wichtiger Punkt wird übersehen.
Das solltest du nicht. Aber nicht, weil Dependency Injection eine schlechte Idee ist. Es ist, weil das falsch macht.
Schauen wir uns diese Dinge mit Code an. Wir werden das machen:
wenn wir ungefähr das Gleiche daraus machen:
Es scheint also Zeitverschwendung zu sein. Es ist, wenn Sie es auf diese Weise tun. Dies ist nicht die beste Verwendung der Abhängigkeitsinjektion. Es ist nicht einmal die beste Verwendung einer Klasse.
Was wäre, wenn wir stattdessen Folgendes hätten:
Und jetzt
application
ist es kostenlos, das zu aktivieren und zu deaktivieren,profile
ohne etwas Besonderes darüber wissen zu müssen, was sichsetting
tatsächlich ändert. Warum ist das gut? Falls Sie die Einstellung ändern müssen. Dasapplication
ist von diesen Änderungen abgeschirmt, so dass Sie in einem sicheren, geschlossenen Raum verrückt werden können, ohne dass Sie zusehen müssen, wie alles kaputt geht, sobald Sie etwas berühren.Dies folgt dem vom Verhaltensprinzip getrennten Aufbau . Das DI-Muster ist hier einfach. Bauen Sie alles, was Sie brauchen, so niedrig wie möglich, verdrahten Sie sie und starten Sie das gesamte Verhalten mit einem einzigen Aufruf.
Das Ergebnis ist, dass Sie einen separaten Ort haben, um zu entscheiden, was mit was verbunden ist, und einen anderen Ort, um zu verwalten, was was mit was sagt.
Versuchen Sie das an etwas, das Sie im Laufe der Zeit pflegen müssen und sehen Sie, ob es nicht hilft.
quelle
Wenn Sie als Kunde einen Mechaniker beauftragen, etwas mit Ihrem Auto zu tun, erwarten Sie dann, dass dieser Mechaniker ein Auto von Grund auf neu baut, um dann damit zu arbeiten? Nein, Sie geben dem Mechaniker das Auto, an dem Sie arbeiten möchten .
Wenn Sie als Garagenbesitzer einen Mechaniker beauftragen, etwas mit einem Auto zu tun, erwarten Sie, dass der Mechaniker seinen eigenen Schraubenzieher / Schraubenschlüssel / Autoteile herstellt? Nein, Sie stellen dem Mechaniker die Teile / Werkzeuge zur Verfügung, die er verwenden muss
Warum machen wir das? Nun, denk darüber nach. Sie sind ein Garagenbesitzer, der jemanden einstellen möchte, um Ihr Mechaniker zu werden. Du wirst ihnen beibringen, ein Mechaniker zu sein (= du wirst den Code schreiben).
Was wird einfacher:
Es hat enorme Vorteile, wenn Ihr Mechaniker nicht alles von Grund auf neu erstellt:
Wenn Sie einen erfahrenen Mechaniker einstellen und ausbilden, werden Sie einen Mitarbeiter finden, der mehr kostet, mehr Zeit für die Durchführung einer eigentlich einfachen Aufgabe benötigt und immer wieder umgeschult werden muss, wenn eine der vielen Aufgaben erforderlich ist aktualisiert werden.
Die Entwicklungsanalogie lautet: Wenn Sie Klassen mit fest codierten Abhängigkeiten verwenden, werden Sie am Ende schwer zu pflegende Klassen haben, die (
Settings
in Ihrem Fall) immer dann neu entwickelt / geändert werden müssen, wenn eine neue Version des Objekts erstellt wird Sie müssen eine interne Logik entwickeln, damit die Klasse verschiedene Objekttypen erstellenSettings
kann.Darüber hinaus muss jeder, der Ihre Klasse konsumiert, jetzt auch die Klasse auffordern, das richtige Objekt zu erstellen , anstatt der Klasse
Settings
einfach jedesSettings
Objekt übergeben zu können, das sie passieren möchte. Dies bedeutet eine zusätzliche Entwicklung für den Verbraucher, um herauszufinden, wie er die Klasse auffordern kann, das richtige Werkzeug zu erstellen.Ja, die Abhängigkeitsinversion erfordert etwas mehr Aufwand beim Schreiben, anstatt die Abhängigkeit hart zu codieren. Ja, es ist ärgerlich, mehr tippen zu müssen.
Dies ist jedoch dasselbe Argument wie die Auswahl von Hardcode-Literalwerten, da "das Deklarieren von Variablen mehr Aufwand erfordert". Technisch korrekt, aber die Vorteile überwiegen die Nachteile um mehrere Größenordnungen .
Der Vorteil der Abhängigkeitsinversion tritt beim Erstellen der ersten Version der Anwendung nicht auf. Der Vorteil der Abhängigkeitsinversion tritt auf, wenn Sie diese ursprüngliche Version ändern oder erweitern müssen. Und täuschen Sie sich nicht in der Annahme, dass Sie es beim ersten Mal richtig machen und den Code nicht erweitern / ändern müssen. Sie müssen die Dinge ändern.
Dies wirkt sich nicht auf die Laufzeitleistung der Anwendung aus. Dies wirkt sich jedoch massiv auf die Entwicklungszeit (und damit auf die Leistung) des Entwicklers aus .
quelle
Wie bei allen Mustern gilt es, nach dem Warum zu fragen, um aufgeblähte Designs zu vermeiden.
Für die Abhängigkeitsinjektion ist dies leicht zu erkennen, wenn man sich die beiden wohl wichtigsten Facetten des OOP-Designs vor Augen hält ...
Geringe Kopplung
Kopplung in der Computerprogrammierung :
Sie möchten eine geringe Kopplung erreichen. Zwei Dinge sind eng miteinander verbunden: Wenn Sie das eine ändern, müssen Sie höchstwahrscheinlich das andere ändern. Bugs oder Restriktionen in der einen führen wahrscheinlich zu Bugs / Restriktionen in der anderen. und so weiter.
Eine Klasse, die Objekte der anderen instanziiert, ist eine sehr starke Kopplung, weil die eine über die Existenz der anderen Bescheid wissen muss; Es muss wissen, wie es instanziiert werden soll (welche Argumente der Konstruktor benötigt), und diese Argumente müssen verfügbar sein, wenn der Konstruktor aufgerufen wird. Abhängig davon, ob die Sprache explizit dekonstruiert werden muss (C ++), führt dies zu weiteren Komplikationen. Wenn Sie neue Klassen einführen (z. B. a
NextSettings
oder was auch immer), müssen Sie zur ursprünglichen Klasse zurückkehren und ihren Konstruktoren weitere Aufrufe hinzufügen.Hoher Zusammenhalt
Zusammenhalt :
Dies ist die andere Seite der Medaille. Wenn Sie sich eine Codeeinheit (eine Methode, eine Klasse, ein Paket usw.) ansehen, möchten Sie, dass sich der gesamte Code in dieser Einheit befindet, um so wenig Verantwortlichkeiten wie möglich zu haben.
Ein grundlegendes Beispiel hierfür wäre das MVC-Muster: Sie trennen das Domänenmodell klar von der Ansicht (GUI) und einer Kontrollschicht, die diese zusammenhält.
Dies vermeidet das Aufblähen von Code, wenn Sie große Stücke erhalten, die viele verschiedene Aufgaben ausführen. Wenn Sie einen Teil davon ändern möchten, müssen Sie auch alle anderen Funktionen im Auge behalten und versuchen, Fehler usw. zu vermeiden. und Sie programmieren sich schnell in ein Loch, in dem es sehr schwer ist, herauszukommen.
Mit der Abhängigkeitsinjektion delegieren Sie das Erstellen oder Verfolgen von Abhängigkeiten an alle Klassen (oder Konfigurationsdateien), die Ihre DI implementieren. Andere Klassen werden nicht viel kümmern , was genau los ist - sie werden mit einigen generischen Schnittstellen arbeiten und haben keine Ahnung , was die tatsächliche Umsetzung ist, das heißt , sie sind nicht verantwortlich für die anderen Sachen.
quelle
Sie sollten Techniken verwenden, um die Probleme zu lösen, die sie gut lösen können, wenn Sie diese Probleme haben. Abhängigkeitsinversion und Injektion sind nicht unterschiedlich.
Abhängigkeitsinversion oder -injektion ist eine Technik, mit der Ihr Code entscheiden kann, welche Implementierung einer Methode zur Laufzeit aufgerufen wird. Dies maximiert die Vorteile einer späten Bindung. Die Technik ist erforderlich, wenn die Sprache das Ersetzen von Nicht-Instanz-Funktionen zur Laufzeit nicht unterstützt. Beispielsweise fehlt in Java ein Mechanismus, um Aufrufe einer statischen Methode durch Aufrufe einer anderen Implementierung zu ersetzen. Im Gegensatz zu Python müssen Sie zum Ersetzen des Funktionsaufrufs nur den Namen an eine andere Funktion binden (die Variable, die die Funktion enthält, neu zuweisen).
Warum sollten wir die Implementierung der Funktion variieren wollen? Es gibt zwei Hauptgründe:
Möglicherweise möchten Sie auch die Inversion von Steuercontainern beachten. Dies ist eine Technik, die Ihnen helfen soll, riesige, verworrene Konstruktionsbäume zu vermeiden, die wie folgt aussehen:
Sie können Ihre Klassen registrieren und dann die Konstruktion für Sie durchführen:
Beachten Sie, dass es am einfachsten ist, wenn die registrierten Klassen zustandslose Singletons sein können .
Achtung!
Beachten Sie, dass die Abhängigkeitsinversion nicht Ihre erste Antwort für die Entkopplungslogik sein sollte. Suchen Sie nach Möglichkeiten, stattdessen die Parametrisierung zu verwenden. Betrachten Sie diese Pseudocode-Methode zum Beispiel:
Wir könnten die Abhängigkeitsinversion für einige Teile dieser Methode verwenden:
Das sollten wir aber zumindest nicht ganz. Beachten Sie, dass wir mit eine stateful- Klasse erstellt haben
Querier
. Es enthält jetzt einen Verweis auf ein im Wesentlichen globales Verbindungsobjekt. Dies führt zu Problemen wie dem schwierigen Verständnis des Gesamtzustands des Programms und der Koordinierung der verschiedenen Klassen. Beachten Sie auch, dass wir gezwungen sind, den Abfrager oder die Verbindung auszutricksen, wenn wir die Mittelungslogik testen möchten. Ein besserer Ansatz wäre, die Parametrisierung zu erhöhen :Und die Verbindung würde auf einer noch höheren Ebene verwaltet, die für den gesamten Vorgang verantwortlich ist und weiß, was mit dieser Ausgabe zu tun ist.
Jetzt können wir die Mittelungslogik völlig unabhängig von der Abfrage testen und sie darüber hinaus in einer Vielzahl von Situationen einsetzen. Wir könnten uns fragen, ob wir überhaupt die
MyQuerier
undAverager
-Objekte benötigen , und vielleicht lautet die Antwort, dass dies nicht der Fall ist, wenn wir nicht beabsichtigen, einen Komponententest durchzuführenStuffDoer
, und ein KomponententestStuffDoer
wäre absolut vernünftig, da er so eng mit der Datenbank verbunden ist. Es könnte sinnvoller sein, sich nur von Integrationstests abdecken zu lassen. In diesem Fall könnten wir fein machen seinfetchAboveMin
undaverageData
in statischen Methoden.quelle