Ich versuche gerade, SOLID herauszufinden. Das Prinzip der Abhängigkeitsinversion bedeutet also, dass zwei Klassen nicht direkt, sondern über Schnittstellen kommunizieren sollten. Beispiel: Wenn class A
eine Methode vorhanden ist, die einen Zeiger auf ein Objekt vom Typ erwartet class B
, sollte diese Methode tatsächlich ein Objekt vom Typ erwarten abstract base class of B
. Dies hilft auch beim Öffnen / Schließen.
Vorausgesetzt, ich habe das richtig verstanden, wäre meine Frage, ob es eine gute Praxis ist, dies auf alle Klasseninteraktionen anzuwenden, oder ob ich versuchen sollte, in Ebenen zu denken ?
Der Grund, warum ich skeptisch bin, ist, dass wir für die Befolgung dieses Prinzips einen gewissen Preis zahlen. Angenommen, ich muss die Funktion implementieren Z
. Nach der Analyse komme ich zu dem Schluss, dass das Merkmal Z
aus Funktionalität besteht A
, B
und C
. Ich erstelle eine Fassade Klasse Z
, dass durch Schnittstellen verwendet Klassen A
, B
und C
. Ich beginne die Codierung der Umsetzung und an einem gewissen Punkt , den ich erkennen , dass Aufgabe Z
der Funktionalität tatsächlich besteht A
, B
und D
. Jetzt muss ich das C
Interface, den C
Klassenprototyp, ausrangieren und separate D
Interfaces und Klassen schreiben . Ohne Schnittstellen hätte nur die Klasse ersetzt werden müssen.
Mit anderen Worten, um etwas zu ändern, muss ich 1. den Aufrufer 2. die Schnittstelle 3. die Deklaration 4. die Implementierung ändern. In einer Python direkt gekoppelten Implementierung müsste ich nur die Implementierung ändern .
Antworten:
In vielen Cartoons oder anderen Medien werden die Kräfte von Gut und Böse oft durch einen Engel und einen Dämon dargestellt, die auf den Schultern des Charakters sitzen. In unserer Geschichte hier haben wir anstelle von Gut und Böse SOLID auf einer Schulter und YAGNI (Du wirst es nicht brauchen!) Sitzt auf der anderen.
Optimale SOLID-Prinzipien eignen sich am besten für riesige, komplexe, ultrakonfigurierbare Unternehmenssysteme. Für kleinere oder spezifischere Systeme ist es nicht angebracht, alles lächerlich flexibel zu gestalten, da sich die Zeit, die Sie mit dem Abstrahieren von Dingen verbringen, nicht als Vorteil erweist.
Das Übergeben von Schnittstellen anstelle von konkreten Klassen bedeutet beispielsweise manchmal, dass Sie das Lesen aus einer Datei einfach gegen einen Netzwerkstrom austauschen können. Für eine große Anzahl von Softwareprojekten ist diese Flexibilität jedoch nicht immer erforderlich. Sie können auch konkrete Dateiklassen absolvieren und es als Tag bezeichnen und Ihre Gehirnzellen schonen.
Ein Teil der Kunst der Softwareentwicklung besteht darin, ein gutes Gefühl dafür zu haben, was sich im Laufe der Zeit wahrscheinlich ändern wird und was nicht. Verwenden Sie für die Dinge, die sich wahrscheinlich ändern, die Schnittstellen und andere SOLID-Konzepte. Verwenden Sie YAGNI und übergeben Sie einfach konkrete Typen, vergessen Sie die Factory-Klassen, vergessen Sie das gesamte Anschließen und Konfigurieren der Laufzeit usw. und vergessen Sie viele der SOLID-Abstraktionen. Meiner Erfahrung nach hat sich der YAGNI-Ansatz weitaus häufiger als unkorrekt erwiesen.
quelle
File
, und stattdessen wird es einenIFile
Job erfordern, der erledigt wird". Dann können sie einen Netzwerk-Stream nicht einfach ersetzen, weil sie die Schnittstelle überfordert haben, und es gibt Vorgänge,IFile
die die Methode nicht einmal verwendet, die nicht für Sockets gelten, sodass ein Socket nicht implementiert werden kannIFile
. Eines der Dinge, für die DI kein Königsweg ist, ist die Erfindung der richtigen Abstraktionen (Schnittstellen) :-)Mit den Worten des Laien:
Das Anwenden des DIP ist einfach und macht Spaß . Das Design nicht gleich beim ersten Versuch richtig zu machen, ist nicht genug, um das DIP ganz aufzugeben.
Auf der anderen Seite kann die Programmierung mit Schnittstellen und OOD die Freude an dem manchmal veralteten Handwerk der Programmierung zurückbringen.
Einige Leute sagen, es fügt Komplexität hinzu, aber ich denke, dass die Opossite wahr ist. Auch für kleine Projekte. Es erleichtert das Testen / Verspotten. Es führt dazu, dass Ihr Code weniger
case
Anweisungen enthält oder verschachtelt istifs
. Es reduziert die zyklomatische Komplexität und bringt Sie zum Nachdenken. Dadurch wird die Programmierung realistischer in Bezug auf Design und Fertigung.quelle
Verwenden Sie die Abhängigkeitsinversion dort, wo es Sinn macht.
Ein extremes Gegenbeispiel ist die in vielen Sprachen enthaltene Klasse "string". Es stellt ein primitives Konzept dar, im Wesentlichen ein Array von Zeichen. Angenommen, Sie könnten diese Kernklasse ändern, ist es nicht sinnvoll, DI hier zu verwenden, da Sie den internen Zustand niemals durch etwas anderes ersetzen müssen.
Wenn Sie eine Gruppe von Objekten haben, die intern in einem Modul verwendet werden und die anderen Modulen nicht ausgesetzt oder nirgendwo wiederverwendet werden, lohnt es sich wahrscheinlich nicht, DI zu verwenden.
Es gibt zwei Stellen, an denen DI meiner Meinung nach automatisch verwendet werden sollte:
In den Modulen konzipiert für die Erweiterung. Wenn der gesamte Zweck eines Moduls darin besteht, es zu erweitern und das Verhalten zu ändern, ist es vollkommen sinnvoll, DI von Anfang an einzubacken.
In Modulen, die Sie für den Zweck der Wiederverwendung von Code überarbeiten. Vielleicht haben Sie eine Klasse programmiert, um etwas zu tun, und später festgestellt, dass Sie diesen Code mit einem Refactor an anderer Stelle einsetzen können, und dies ist unbedingt erforderlich . Das ist ein großartiger Kandidat für DI und andere Erweiterungsänderungen.
Die Schlüssel werden dort verwendet, wo sie benötigt werden, da dies zusätzliche Komplexität mit sich bringt und Sie sicherstellen, dass Sie diesen Bedarf entweder anhand der technischen Anforderungen (Punkt 1) oder der Überprüfung des quantitativen Codes (Punkt 2) messen können .
DI ist ein großartiges Tool, aber genau wie jedes * Tool kann es überbeansprucht oder missbraucht werden.
* Ausnahme von der obigen Regel: Eine Säbelsäge ist das perfekte Werkzeug für jede Arbeit. Wenn es Ihr Problem nicht behebt, wird es entfernt. Permanent.
quelle
String
Typs einige Vorteile bietet , sind in vielen Fällen alternative Darstellungen hilfreich, wenn der Typ über eine Reihe guter virtueller Operationen verfügt (z. B. Kopieren eines Teilstrings in einen bestimmten Teil von ashort[]
, Melden Sie, ob a Teilzeichenfolge enthält nur ASCII oder enthält möglicherweise nur ASCII. Versuchen Sie, eine Teilzeichenfolge, von der angenommen wird, dass sie nur ASCII enthält, in einen bestimmten Teil von a zu kopierenbyte[]
.Es scheint mir, dass die ursprüngliche Frage einen Teil des DIP-Punktes verfehlt.
Um das DIP wirklich nutzen zu können, müssen Sie zuerst die Klasse Z erstellen und die Funktionalität der Klassen A, B und C aufrufen (die noch nicht entwickelt wurden). Auf diese Weise erhalten Sie die API für die Klassen A, B und C. Anschließend erstellen Sie die Klassen A, B und C und geben die Details ein. Sie sollten effektiv die Abstraktionen erstellen, die Sie benötigen, während Sie die Klasse Z erstellen, basierend auf den Anforderungen der Klasse Z. Sie können sogar Tests um die Klasse Z schreiben, bevor die Klassen A, B oder C überhaupt geschrieben werden.
Denken Sie daran, dass das DIP besagt, dass "High-Level-Module nicht von Low-Level-Modulen abhängen sollten. Beide sollten von Abstraktionen abhängen."
Wenn Sie herausgefunden haben, was die Klasse Z braucht und wie sie bekommen will, was sie braucht, können Sie die Details eintragen. Sicher, manchmal müssen Änderungen an Klasse Z vorgenommen werden, aber in 99% der Fälle ist dies nicht der Fall.
Es wird nie eine Klasse D geben, weil Sie herausgefunden haben, dass Z A, B und C benötigt, bevor sie geschrieben wurden. Eine Änderung der Anforderungen ist eine ganz andere Geschichte.
quelle
Die kurze Antwort lautet "fast nie", aber es gibt tatsächlich einige Stellen, an denen das DIP keinen Sinn ergibt:
Fabriken oder Bauunternehmen, deren Aufgabe es ist, Objekte zu erstellen. Dies sind im Wesentlichen die "Blattknoten" in einem System, das die IoC vollständig umfasst. Irgendwann muss etwas tatsächlich Ihre Objekte erstellen und kann nicht von etwas anderem abhängen, um dies zu tun. In vielen Sprachen kann ein IoC-Container dies für Sie tun, aber manchmal müssen Sie es auf die altmodische Weise tun.
Implementierung von Datenstrukturen und Algorithmen. Im Allgemeinen hängen in diesen Fällen die hervorstechenden Merkmale, für die Sie optimieren (z. B. Laufzeit und asymptotische Komplexität), von den verwendeten Datentypen ab. Wenn Sie eine Hash-Tabelle implementieren, müssen Sie wirklich wissen, dass Sie mit einem Array zum Speichern arbeiten, nicht mit einer verknüpften Liste, und nur die Tabelle selbst weiß, wie die Arrays richtig zugeordnet werden. Sie möchten auch kein veränderliches Array übergeben und den Aufrufer dazu bringen, Ihre Hash-Tabelle zu durchbrechen, indem er mit ihrem Inhalt spielt.
Domänenmodellklassen . Diese implementieren Ihre Geschäftslogik, und (in den meisten Fällen) ist es nur sinnvoll, eine Implementierung zu haben, da Sie (in den meisten Fällen) die Software nur für ein Unternehmen entwickeln. Obwohl einige Domänenmodellklassen möglicherweise mit anderen Domänenmodellklassen erstellt werden, wird dies im Allgemeinen von Fall zu Fall erfolgen. Da Domänenmodellobjekte keine Funktionalität enthalten, die sinnvoll nachgeahmt werden kann, bietet der DIP keinen Vorteil für die Testbarkeit oder Wartbarkeit.
Alle Objekte, die als externe API bereitgestellt werden und andere Objekte erstellen müssen, deren Implementierungsdetails Sie nicht öffentlich zugänglich machen möchten. Dies fällt unter die allgemeine Kategorie "Bibliotheksdesign unterscheidet sich vom Anwendungsdesign". Eine Bibliothek oder ein Framework kann DI intern liberal nutzen, muss aber irgendwann tatsächlich arbeiten, da es sich sonst nicht um eine sehr nützliche Bibliothek handelt. Angenommen, Sie entwickeln eine Netzwerkbibliothek. Sie möchten wirklich nicht, dass der Verbraucher eine eigene Implementierung eines Sockets bereitstellen kann. Sie können eine Abstraktion eines Sockets intern verwenden, aber die API, die Sie für Aufrufer bereitstellen, erstellt eigene Sockets.
Unit-Tests und Test-Doppel. Fälschungen und Stummel sollen eine Sache tun und es einfach tun. Wenn Sie eine Fälschung haben, die komplex genug ist, um sich Gedanken darüber zu machen, ob Sie eine Abhängigkeitsinjektion durchführen sollen oder nicht, ist sie wahrscheinlich zu komplex (vielleicht, weil sie eine Schnittstelle implementiert, die auch viel zu komplex ist).
Es könnte mehr geben; das sind die, die ich ziemlich häufig sehe .
quelle
Einige Anzeichen, dass Sie DIP auf einer zu kleinen Ebene anwenden, auf der es keinen Wert liefert:
Wenn Sie dies sehen, ist es möglicherweise besser, wenn Sie Z C direkt aufrufen und die Schnittstelle überspringen.
Ich denke auch nicht, dass Methodendekoration durch ein Abhängigkeitsinjektions- / dynamisches Proxy-Framework (Spring, Java EE) genauso wie echtes SOLID DIP funktioniert. Nach Meinung der Java EE-Community ist es eine Verbesserung, dass Sie keine Foo / FooImpl-Paare mehr benötigen, wie Sie es gewohnt sind ( Referenz ). Im Gegensatz dazu unterstützt Python die Funktionsdekoration als erstklassiges Sprachmerkmal.
Siehe auch diesen Blog-Beitrag .
quelle
Wenn Sie Ihre Abhängigkeiten immer umkehren, stehen alle Abhängigkeiten auf dem Kopf. Das heißt, wenn Sie mit chaotischem Code mit einem Knoten von Abhängigkeiten angefangen haben, ist das, was Sie immer noch (wirklich) haben, nur invertiert. An dieser Stelle tritt das Problem auf, dass bei jeder Änderung an einer Implementierung auch die Benutzeroberfläche geändert werden muss.
Der Punkt der Abhängigkeitsinversion ist, dass Sie die Abhängigkeiten, die die Dinge verwickeln, selektiv invertieren. Diejenigen, die von A nach B nach C gehen sollten, tun dies immer noch. Es sind diejenigen, die von C nach A gehen, die jetzt von A nach C gehen.
Das Ergebnis sollte ein zyklenfreies Abhängigkeitsdiagramm sein - eine DAG. Es gibt verschiedene Tools , die diese Eigenschaft prüfen und das Diagramm zeichnen.
Eine ausführlichere Erklärung finden Sie in diesem Artikel :
quelle