Prinzip der Abhängigkeitsinversion



Alles Wissen, das die Menschen im Laufe der Jahrhunderte über Prinzip der Abhängigkeitsinversion angesammelt haben, ist jetzt im Internet verfügbar, und wir haben es für Sie auf möglichst zugängliche Weise zusammengestellt und geordnet. Wir möchten, dass Sie schnell und effizient auf alles zugreifen können, was Sie über Prinzip der Abhängigkeitsinversion wissen möchten, dass Ihre Erfahrung angenehm ist und dass Sie das Gefühl haben, wirklich die Informationen über Prinzip der Abhängigkeitsinversion gefunden zu haben, nach denen Sie gesucht haben.

Um unsere Ziele zu erreichen, haben wir uns nicht nur bemüht, die aktuellsten, verständlichsten und wahrheitsgetreuesten Informationen über Prinzip der Abhängigkeitsinversion zu erhalten, sondern wir haben auch dafür gesorgt, dass das Design, die Lesbarkeit, die Ladegeschwindigkeit und die Benutzerfreundlichkeit der Seite so angenehm wie möglich sind, damit Sie sich auf das Wesentliche konzentrieren können, nämlich alle verfügbaren Daten und Informationen über Prinzip der Abhängigkeitsinversion zu kennen, ohne sich um irgendetwas anderes kümmern zu müssen, das haben wir bereits für Sie erledigt. Wir hoffen, dass wir unser Ziel erreicht haben und dass Sie die gewünschten Informationen über Prinzip der Abhängigkeitsinversion gefunden haben. Wir heißen Sie also willkommen und ermutigen Sie, die Erfahrung der Nutzung von scientiade.com weiterhin zu genießen.

In objektorientiertem Design , das Abhängigkeitsinversionsprinzip ist eine spezielle Form von loser Kopplung Softwaremodule . Wenn dieses Prinzip befolgt wird, werden die herkömmlichen Abhängigkeitsbeziehungen, die von hochrangigen, richtlinienfestsetzenden Modulen zu niederstufigen Abhängigkeitsmodulen aufgebaut werden, umgekehrt, wodurch hochrangige Module unabhängig von den Implementierungsdetails der niederwertigen Module gemacht werden. Das Prinzip besagt:

  1. High-Level-Module sollten nichts aus Low-Level-Modulen importieren. Beide sollten von Abstraktionen (zB Schnittstellen) abhängen.
  2. Abstraktionen sollten nicht von Details abhängen. Details (konkrete Implementierungen) sollten von Abstraktionen abhängen.

Durch die Vorgabe, dass sowohl High-Level- als auch Low-Level-Objekte von derselben Abstraktion abhängen müssen, kehrt dieses Entwurfsprinzip die Denkweise einiger Leute über objektorientierte Programmierung um.

Die Idee hinter den Punkten A und B dieses Prinzips ist, dass bei der Gestaltung der Interaktion zwischen einem High-Level-Modul und einem Low-Level-Modul die Interaktion als abstrakte Interaktion zwischen ihnen gedacht werden sollte. Dies hat nicht nur Auswirkungen auf das Design des High-Level-Moduls, sondern auch auf das Low-Level-Modul: Das Low-Level-Modul sollte unter Berücksichtigung der Interaktion gestaltet werden und es kann erforderlich sein, seine Benutzeroberfläche zu ändern.

In vielen Fällen kann die Interaktion an sich als abstraktes Konzept betrachtet werden, um die Kopplung der Komponenten zu reduzieren, ohne zusätzliche Kodierungsmuster einzuführen, was nur ein leichteres und weniger implementierungsabhängiges Interaktionsschema ermöglicht.

Wenn das/die entdeckte(n) abstrakte(n) Interaktionsschema(s) zwischen zwei Modulen generisch ist/sind und eine Verallgemeinerung sinnvoll ist, führt dieses Entwurfsprinzip auch zu dem folgenden Codierungsmuster der Abhängigkeitsinversion.

Traditionelles Schichtmuster

In der herkömmlichen Anwendungsarchitektur sind Komponenten niedrigerer Ebene (zB Utility Layer) so ausgelegt, dass sie von Komponenten höherer Ebene (zB Policy Layer) genutzt werden, die den Aufbau zunehmend komplexer Systeme ermöglichen. In dieser Zusammensetzung hängen Komponenten höherer Ebene direkt von Komponenten niedrigerer Ebene ab, um eine Aufgabe zu erfüllen. Diese Abhängigkeit von untergeordneten Komponenten schränkt die Wiederverwendungsmöglichkeiten der übergeordneten Komponenten ein.

Traditionelles Ebenenmuster.png

Das Ziel des Dependency Inversion Patterns besteht darin, diese stark gekoppelte Verteilung mit der Vermittlung einer abstrakten Schicht zu vermeiden und die Wiederverwendbarkeit von höheren/Policy-Schichten zu erhöhen.

Abhängigkeitsinversionsmuster

Durch das Hinzufügen einer abstrakten Ebene reduzieren sowohl High- als auch Low-Level-Schichten die traditionellen Abhängigkeiten von oben nach unten. Das Konzept der "Inversion" bedeutet jedoch nicht, dass Schichten niedrigerer Ebene direkt von Schichten höherer Ebene abhängen. Beide Schichten sollten von Abstraktionen (Schnittstellen) abhängen, die das von übergeordneten Schichten benötigte Verhalten offenlegen.

DIPLayersPattern.png

Bei einer direkten Anwendung der Abhängigkeitsinversion sind die Zusammenfassungen im Besitz der oberen/Policy-Schichten. Diese Architektur gruppiert die höheren Komponenten/Richtlinien und die Abstraktionen, die niedrigere Dienste definieren, in demselben Paket. Die untergeordneten Schichten werden durch Vererbung/Implementierung dieser abstrakten Klassen oder Schnittstellen erzeugt.

Die Umkehrung der Abhängigkeiten und des Eigentums fördert die Wiederverwendbarkeit der höheren Schichten/Richtlinien. Obere Schichten könnten andere Implementierungen der unteren Dienste verwenden. Wenn die Komponenten der untergeordneten Schicht geschlossen werden oder die Anwendung die Wiederverwendung vorhandener Dienste erfordert, ist es üblich, dass ein Adapter zwischen den Diensten und den Abstraktionen vermittelt.

Verallgemeinerung von Abhängigkeitsinversionsmustern

In vielen Projekten werden das Prinzip und das Muster der Abhängigkeitsinversion als ein einziges Konzept betrachtet, das verallgemeinert, dh auf alle Schnittstellen zwischen Softwaremodulen angewendet werden sollte. Dafür gibt es mindestens zwei Gründe:

  1. Es ist einfacher, ein gutes Denkprinzip als Kodierungsmuster zu sehen. Sobald eine abstrakte Klasse oder ein Interface codiert ist, kann der Programmierer sagen: "Ich habe die Abstraktion erledigt".
  2. Da viele Unit-Test- Tools auf Vererbung angewiesen sind, um Mocking zu erreichen , wurde die Verwendung von generischen Schnittstellen zwischen Klassen (nicht nur zwischen Modulen, wenn es sinnvoll ist, Allgemeinheit zu verwenden) zur Regel.

Wenn das verwendete Mocking-Tool nur auf Vererbung beruht, kann es erforderlich werden, das Abhängigkeitsinversionsmuster breit anzuwenden. Dies hat gravierende Nachteile:

  1. Die bloße Implementierung einer Schnittstelle über eine Klasse reicht nicht aus, um die Kopplung zu reduzieren; nur das Nachdenken über die mögliche Abstraktion von Interaktionen kann zu einem weniger gekoppelten Design führen.
  2. Die Implementierung generischer Schnittstellen überall in einem Projekt erschwert das Verständnis und die Wartung. Bei jedem Schritt wird sich der Leser fragen, was die anderen Implementierungen dieser Schnittstelle sind und die Antwort ist im Allgemeinen: nur Mocks.
  3. Die Schnittstellenverallgemeinerung erfordert mehr Installationscode, insbesondere Fabriken, die im Allgemeinen auf einem Abhängigkeitsinjektions-Framework beruhen.
  4. Die Schnittstellenverallgemeinerung schränkt auch die Verwendung der Programmiersprache ein.

Einschränkungen bei der Generalisierung

Das Vorhandensein von Schnittstellen zum Erreichen des Dependency Inversion Pattern (DIP) hat andere Auswirkungen auf das Design eines objektorientierten Programms :

  • Alle Membervariablen in einer Klasse müssen Interfaces oder Abstracts sein.
  • Alle konkreten Klassenpakete dürfen sich nur über Interfaces oder abstrakte Klassenpakete verbinden.
  • Keine Klasse sollte von einer konkreten Klasse abgeleitet werden.
  • Keine Methode sollte eine implementierte Methode überschreiben.
  • Jede variable Instanziierung erfordert die Implementierung eines Erzeugungsmusters wie der Fabrikmethode oder des Fabrikmusters oder die Verwendung eines Abhängigkeitsinjektions- Frameworks.

Einschränkungen beim Verspotten von Schnittstellen

Die Verwendung von vererbungsbasierten Mocking-Tools führt auch zu Einschränkungen:

  • Statische extern sichtbare Member sollten systematisch auf Dependency Injection setzen, was ihre Implementierung erheblich erschwert.
  • Alle testbaren Methoden sollten eine Schnittstellenimplementierung oder eine Überschreibung einer abstrakten Definition werden.

Zukünftige Richtungen

Prinzipien sind Denkweisen. Muster sind gängige Methoden, um Probleme zu lösen. Bei Codierungsmustern fehlen möglicherweise Programmiersprachenfunktionen.

  • Programmiersprachen werden sich weiterentwickeln, damit sie stärkere und präzisere Nutzungsverträge in mindestens zwei Richtungen durchsetzen können: Durchsetzung von Nutzungsbedingungen (Prä-, Post- und invariante Bedingungen) und zustandsbasierte Schnittstellen. Dies wird wahrscheinlich eine stärkere Anwendung des Abhängigkeitsinversionsmusters in vielen Situationen fördern und möglicherweise vereinfachen.
  • Immer mehr Mocking-Tools verwenden nun Dependency-Injection , um das Problem des Ersetzens statischer und nicht virtueller Member zu lösen. Programmiersprachen werden sich wahrscheinlich weiterentwickeln, um Mocking-kompatiblen Bytecode zu generieren. Eine Richtung wird darin bestehen, die Verwendung nicht virtueller Mitglieder einzuschränken. Die andere besteht darin, zumindest in Testsituationen Bytecode zu generieren, der ein nicht vererbungsbasiertes Mocking ermöglicht.

Implementierungen

Zwei gängige Implementierungen von DIP verwenden eine ähnliche logische Architektur, jedoch mit unterschiedlichen Implikationen.

Eine direkte Implementierung verpackt die Richtlinienklassen mit Dienstabstraktionsklassen in einer Bibliothek. In dieser Implementierung werden High-Level-Komponenten und Low-Level-Komponenten in separate Pakete/Bibliotheken verteilt, wobei die Schnittstellen , die das Verhalten/die Dienste definieren, die von der High-Level-Komponente benötigt werden, Eigentum der Bibliothek der High-Level-Komponente sind und innerhalb der High-Level-Komponentenbibliothek existieren. Die Implementierung der Schnittstelle der High-Level-Komponente durch die Low-Level-Komponente erfordert, dass das Low-Level-Komponentenpaket zur Kompilierung von der High-Level-Komponente abhängt, wodurch die herkömmliche Abhängigkeitsbeziehung umgekehrt wird.

Abhängigkeitsinversion.png

Die Abbildungen 1 und 2 veranschaulichen Code mit derselben Funktionalität, jedoch wurde in Abbildung 2 eine Schnittstelle verwendet, um die Abhängigkeit umzukehren. Die Richtung der Abhängigkeit kann gewählt werden, um die Wiederverwendung von Richtliniencode zu maximieren und zyklische Abhängigkeiten zu beseitigen.

In dieser Version von DIP erschwert die Abhängigkeit der Komponente der unteren Schicht von den Schnittstellen/Abstracts in den Schichten der höheren Ebene die Wiederverwendung der Komponenten der unteren Schicht. Diese Implementierung invertiert stattdessen die traditionelle Abhängigkeit von oben nach unten ins Gegenteil, von unten nach oben.

Eine flexiblere Lösung extrahiert die abstrakten Komponenten in einen unabhängigen Satz von Paketen/Bibliotheken:

DIPLayersPattern v2.png

Die Trennung jeder Schicht in ein eigenes Paket fördert die Wiederverwendung jeder Schicht und sorgt für Robustheit und Mobilität.

Beispiele

Genealogisches Modul

Ein genealogisches System kann Beziehungen zwischen Menschen als Graph der direkten Beziehungen zwischen ihnen darstellen (Vater-Sohn, Vater-Tochter, Mutter-Sohn, Mutter-Tochter, Mann-Frau, Frau-Ehemann usw.). Dies ist sehr effizient und erweiterbar, da es einfach ist, einen Ex-Ehemann oder einen Erziehungsberechtigten hinzuzufügen. Mechanismen sollten ihre Schnittstelle implementieren, nicht die der Richtlinie. Das Diagramm ist falsch.

Aber einige höherstufige Module erfordern möglicherweise eine einfachere Art, das System zu durchsuchen: Jede Person kann Kinder, Eltern, Geschwister (einschließlich Halbbrüder und -schwestern oder nicht), Großeltern, Cousinen usw. haben.

Abhängig von der Verwendung des genealogischen Moduls wird die Kopplung zwischen einem übergeordneten Modul und dem genealogischen Modul durch die Darstellung gemeinsamer Beziehungen als unterschiedliche direkte Eigenschaften (Ausblenden des Graphen) viel einfacher und ermöglicht es, die interne Darstellung der direkten Beziehungen vollständig zu ändern ohne Auswirkungen auf die Module, die sie verwenden. Es erlaubt auch, genaue Definitionen von Geschwistern oder Onkeln in das genealogische Modul einzubetten und so das Prinzip der Alleinverantwortung durchzusetzen .

Wenn schließlich der erste erweiterbare verallgemeinerte Graph-Ansatz am erweiterbarsten erscheint, kann die Verwendung des genealogischen Moduls zeigen, dass eine spezialisiertere und einfachere Beziehungsimplementierung für die Anwendung(en) ausreichend ist und hilft, ein effizienteres System zu schaffen.

In diesem Beispiel führt die Abstraktion der Interaktion zwischen den Modulen zu einer vereinfachten Schnittstelle des untergeordneten Moduls und kann zu einer einfacheren Implementierung desselben führen.

Remote-Dateiserver-Client

Stellen Sie sich vor, Sie müssen einen Client auf einem entfernten Dateiserver implementieren (FTP, Cloud-Speicher ...). Sie können es sich als eine Reihe abstrakter Schnittstellen vorstellen:

  1. Verbindung/Trennung (möglicherweise ist eine Verbindungspersistenzschicht erforderlich)
  2. Schnittstelle zum Erstellen von Ordnern/Tags/Umbenennen/Löschen/Listen
  3. Datei erstellen/ersetzen/umbenennen/löschen/Schnittstelle lesen
  4. Dateisuche
  5. Lösung für gleichzeitiges Ersetzen oder Löschen
  6. Verwaltung des Dateiverlaufs ...

Wenn sowohl lokale Dateien als auch entfernte Dateien dieselben abstrakten Schnittstellen bieten, kann jedes High-Level-Modul, das lokale Dateien verwendet und das Abhängigkeitsinversionsmuster vollständig implementiert, wahllos auf lokale und entfernte Dateien zugreifen.

Die lokale Festplattenfunktionalität verwendet im Allgemeinen Ordner, während der Remotespeicher entweder Ordner oder Tags verwenden kann. Sie müssen entscheiden, wie Sie sie nach Möglichkeit vereinheitlichen.

Bei Remote-Dateien müssen wir möglicherweise nur Create oder Replace verwenden: Remote-Dateien-Updates sind nicht unbedingt sinnvoll, da zufällige Updates im Vergleich zu zufälligen Updates lokaler Dateien zu langsam sind und möglicherweise sehr kompliziert zu implementieren sind). Bei einer Remote-Datei benötigen wir möglicherweise teilweises Lesen und Schreiben (zumindest innerhalb des Remote-Dateimoduls, damit das Herunterladen oder Hochladen nach einer Kommunikationsunterbrechung fortgesetzt werden kann), aber das zufällige Lesen wird nicht angepasst (außer wenn ein lokaler Cache verwendet wird).

Die Dateisuche kann steckbar sein: Die Dateisuche kann sich auf das Betriebssystem verlassen oder insbesondere für die Tag- oder Volltextsuche, mit unterschiedlichen Systemen implementiert werden (OS eingebettet oder separat erhältlich).

Die gleichzeitige Ersetzungs- oder Löschauflösungserkennung kann sich auf die anderen abstrakten Schnittstellen auswirken.

Beim Entwerfen des Remote-Dateiserver-Clients für jede konzeptionelle Schnittstelle müssen Sie sich fragen, welchen Servicegrad Ihre High-Level-Module benötigen (nicht alle erforderlich) und nicht nur, wie die Remote-Dateiserver-Funktionalitäten implementiert werden, sondern auch, wie die Datei erstellt wird Dienste in Ihrer Anwendung kompatibel zwischen bereits implementierten Dateidiensten (lokale Dateien, vorhandene Cloud-Clients) und Ihrem neuen Remote-Dateiserver-Client.

Nachdem Sie die erforderlichen abstrakten Schnittstellen entworfen haben, sollte Ihr Remote-Dateiserver-Client diese Schnittstellen implementieren. Und weil Sie wahrscheinlich einige lokale Funktionalitäten eingeschränkt haben, die in einer lokalen Datei vorhanden sind (z. B. Dateiaktualisierung), müssen Sie möglicherweise Adapter für lokale oder andere vorhandene, verwendete Remote-Dateizugriffsmodule schreiben , die jeweils die gleichen abstrakten Schnittstellen bieten. Sie müssen auch Ihren eigenen Dateizugriffs-Enumerator schreiben, der es ermöglicht, alle dateikompatiblen Systeme abzurufen, die auf Ihrem Computer verfügbar und konfiguriert sind.

Sobald Sie dies tun, kann Ihre Anwendung ihre Dokumente lokal oder aus der Ferne transparent speichern. Oder einfacher gesagt, das High-Level-Modul, das die neuen Dateizugriffsschnittstellen verwendet, kann undeutlich in lokalen oder entfernten Dateizugriffsszenarien verwendet werden, wodurch es wiederverwendbar wird.

Beachten Sie, dass viele Betriebssysteme mit der Implementierung dieser Art von Funktionalitäten begonnen haben und Ihre Arbeit möglicherweise begrenzt ist, um Ihren neuen Client an diese bereits abstrahierten Modelle anzupassen.

Wenn Sie sich in diesem Beispiel das Modul als einen Satz abstrakter Schnittstellen vorstellen und andere Module an diesen Satz von Schnittstellen anpassen, können Sie eine gemeinsame Schnittstelle für viele Dateispeichersysteme bereitstellen.

Model View Controller

Beispiel für DIP

UI- und ApplicationLayer-Pakete enthalten hauptsächlich konkrete Klassen. Controller enthält Zusammenfassungen/Schnittstellentypen. Die Benutzeroberfläche verfügt über eine Instanz von ICustomerHandler. Alle Pakete sind physisch getrennt. Im ApplicationLayer gibt es eine konkrete Implementierung, die die Page-Klasse verwendet. Instanzen dieser Schnittstelle werden dynamisch von einer Factory erstellt (möglicherweise im gleichen Controllers-Paket). Die konkreten Typen Page und CustomerHandler hängen nicht voneinander ab; beide hängen von ICustomerHandler ab.

Der direkte Effekt besteht darin, dass die Benutzeroberfläche nicht auf den ApplicationLayer oder ein konkretes Paket verweisen muss, das den ICustomerHandler implementiert. Die Betonklasse wird mit Reflection geladen . Die konkrete Implementierung könnte jederzeit durch eine andere konkrete Implementierung ersetzt werden, ohne die UI-Klasse zu ändern. Eine weitere interessante Möglichkeit besteht darin, dass die Page-Klasse eine Schnittstelle IPageViewer implementiert, die als Argument an ICustomerHandler-Methoden übergeben werden könnte. Dann könnte die konkrete Implementierung ohne konkrete Abhängigkeit mit der UI kommunizieren. Auch hier sind beide durch Schnittstellen verbunden.

Verwandte Muster

Als Beispiel für das Adaptermuster kann auch die Anwendung des Dependency-Inversion-Prinzips angesehen werden . Das heißt, die High-Level-Klasse definiert ihre eigene Adapterschnittstelle, die die Abstraktion ist, von der die anderen High-Level-Klassen abhängen. Die angepasste Implementierung hängt auch notwendigerweise von derselben Adapterschnittstellenabstraktion ab, während sie durch Verwendung von Code aus ihrem eigenen Low-Level-Modul implementiert werden kann. Das High-Level-Modul hängt nicht vom Low-Level-Modul ab, da es die Low-Level-Funktionalität nur indirekt über die Adapterschnittstelle verwendet, indem es polymorphe Methoden auf die Schnittstelle aufruft, die durch die angepasste Implementierung und ihr Low-Level-Modul implementiert werden.

Verschiedene Muster wie Plugin , Service Locator oder Dependency Injection werden verwendet, um die Laufzeitbereitstellung der ausgewählten Low-Level-Komponentenimplementierung für die High-Level-Komponente zu erleichtern.

Geschichte

Das Dependency Inversion-Prinzip wurde von Robert C. Martin postuliert und in mehreren Veröffentlichungen beschrieben, darunter das Paper Object Oriented Design Quality Metrics: an analysis of Dependencies , ein im C++ Report im Mai 1996 erschienener Artikel mit dem Titel The Dependency Inversion Principle und die Bücher Agile Softwareentwicklung, Prinzipien, Muster und Praktiken und Agile Prinzipien, Muster und Praktiken in C# .

Siehe auch

Verweise

Externe Links

Opiniones de nuestros usuarios

Diana Frey

Endlich ein Artikel über Prinzip der Abhängigkeitsinversion, der leicht zu lesen ist.

Charlotte Moser

Es ist schon lange her, dass ich einen Artikel über Prinzip der Abhängigkeitsinversion gesehen habe, der so didaktisch geschrieben war. Das gefällt mir

Melanie Ludwig

Dieser Beitrag über Prinzip der Abhängigkeitsinversion hat mir eine Wette eingebracht, was nicht weniger als ein gutes Ergebnis ist., Richtig

Elisabeth Janssen

Mein Vater hat mich herausgefordert, Prinzip der Abhängigkeitsinversion zu machen.