Was ist das Prinzip der Abhängigkeitsinversion und warum ist es wichtig?


177

Was ist das Prinzip der Abhängigkeitsinversion und warum ist es wichtig?



3
Lächerliche Menge an Antworten hier unter Verwendung der Begriffe "High Level" und "Low Level" von Wikipedia. Diese Begriffe sind nicht zugänglich und führen viele Leser auf diese Seite. Wenn Sie Wikipedia wieder erbrechen wollen, definieren Sie diese Begriffe bitte in Ihren Antworten , um den Kontext anzugeben!
8bitjunkie

Antworten:


106

Schauen Sie sich dieses Dokument an: Das Prinzip der Abhängigkeitsinversion .

Grundsätzlich heißt es:

  • High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beides sollte von Abstraktionen abhängen.
  • Abstraktionen sollten niemals von Details abhängen. Details sollten von Abstraktionen abhängen.

Kurz gesagt, warum dies wichtig ist: Änderungen sind riskant. Indem Sie von einem Konzept anstelle einer Implementierung abhängen, reduzieren Sie den Änderungsbedarf an Anrufstandorten.

Das DIP reduziert effektiv die Kopplung zwischen verschiedenen Codeteilen. Die Idee ist, dass, obwohl es viele Möglichkeiten gibt, beispielsweise eine Protokollierungsfunktion zu implementieren, die Art und Weise, wie Sie sie verwenden würden, zeitlich relativ stabil sein sollte. Wenn Sie eine Schnittstelle extrahieren können, die das Konzept der Protokollierung darstellt, sollte diese Schnittstelle zeitlich viel stabiler sein als ihre Implementierung, und Anrufstandorte sollten weniger von Änderungen betroffen sein, die Sie vornehmen können, während Sie diesen Protokollierungsmechanismus beibehalten oder erweitern.

Indem Sie die Implementierung auch von einer Schnittstelle abhängig machen, können Sie zur Laufzeit auswählen, welche Implementierung für Ihre spezielle Umgebung besser geeignet ist. Je nach Fall kann dies auch interessant sein.


31
Diese Antwort sagt nicht aus, warum DIP wichtig ist oder was DIP ist. Ich habe das offizielle DIP-Dokument gelesen und denke, dass dies wirklich ein schlechtes und ungerechtfertigtes "Prinzip" ist, da es auf einer fehlerhaften Annahme beruht: dass High-Level-Module wiederverwendbar sind.
Rogério

3
Betrachten Sie ein Abhängigkeitsdiagramm für einige Objekte. Wenden Sie DIP auf die Objekte an. Jetzt ist jedes Objekt unabhängig von der Implementierung der anderen Objekte. Unit-Tests sind jetzt einfach. Ein späteres Refactoring zur Wiederverwendung ist möglich. Designänderungen haben nur sehr begrenzte Änderungsbereiche. Designprobleme kaskadieren nicht. Siehe auch das AI-Muster "Blackboard" für die Inversion der Datenabhängigkeit. Zusammen sehr leistungsfähige Tools, um die Software verständlich, wartbar und zuverlässig zu machen. Ignorieren Sie in diesem Zusammenhang die Abhängigkeitsinjektion. Es hat nichts damit zu tun.
Tim Williscroft

6
Eine Klasse A, die eine Klasse B verwendet, bedeutet nicht, dass A von der Implementierung von B "abhängt"; es hängt nur von der öffentlichen Schnittstelle von B ab. Das Hinzufügen einer separaten Abstraktion, von der sowohl A als auch B jetzt abhängen, bedeutet nur, dass A keine Abhängigkeit mehr von der öffentlichen Schnittstelle von B zur Kompilierungszeit hat. Die Isolierung zwischen Einheiten kann ohne diese zusätzlichen Abstraktionen leicht erreicht werden. Dafür gibt es in Java und .NET spezielle Verspottungswerkzeuge, die alle Situationen behandeln (statische Methoden, Konstruktoren usw.). Durch das Anwenden von DIP wird Software in der Regel komplexer, weniger wartbar und nicht mehr testbar.
Rogério

1
Was ist dann die Umkehrung der Kontrolle? ein Weg, um eine Abhängigkeitsinversion zu erreichen?
user20358

2
@ Rogerio, siehe Derek Greer Antwort unten, er erklärt es. AFAIK, DIP sagt, dass es A ist, der vorschreibt, was A braucht, und nicht B, der sagt, was A braucht. Die Schnittstelle, die A benötigt, sollte also nicht von B angegeben werden, sondern für A.
ejaenv

144

Die Bücher Agile Softwareentwicklung, Prinzipien, Muster und Praktiken sowie Agile Prinzipien, Muster und Praktiken in C # sind die besten Ressourcen, um die ursprünglichen Ziele und Motivationen hinter dem Prinzip der Abhängigkeitsinversion vollständig zu verstehen. Der Artikel "The Dependency Inversion Principle" ist ebenfalls eine gute Ressource, aber aufgrund der Tatsache, dass es sich um eine komprimierte Version eines Entwurfs handelt, der schließlich in die zuvor erwähnten Bücher eingegangen ist, lässt er einige wichtige Diskussionen über das Konzept von a aus Paket- und Schnittstellenbesitz, die der Schlüssel zur Unterscheidung dieses Prinzips von den allgemeineren Empfehlungen zum "Programmieren auf eine Schnittstelle, nicht auf eine Implementierung" sind, die im Buch Design Patterns (Gamma, et al.) enthalten sind.

Um eine Zusammenfassung zu liefern, geht es beim Prinzip der Abhängigkeitsinversion in erster Linie darum , die herkömmliche Richtung von Abhängigkeiten von Komponenten "höherer Ebene" zu Komponenten "niedrigerer Ebene" umzukehren, sodass Komponenten "niedrigerer Ebene" von den Schnittstellen abhängen, deren Eigentümer die Komponenten "höherer Ebene" sind . (Hinweis: Die Komponente "höhere Ebene" bezieht sich hier auf die Komponente, die externe Abhängigkeiten / Dienste erfordert, nicht unbedingt auf ihre konzeptionelle Position innerhalb einer geschichteten Architektur.) Dabei wird die Kopplung nicht so stark reduziert , sondern von Komponenten verschoben , die theoretisch sind weniger wertvoll für Komponenten, die theoretisch wertvoller sind.

Dies wird erreicht, indem Komponenten entworfen werden, deren externe Abhängigkeiten in Form einer Schnittstelle ausgedrückt werden, für die der Verbraucher der Komponente eine Implementierung bereitstellen muss. Mit anderen Worten, die definierten Schnittstellen drücken aus, was von der Komponente benötigt wird, nicht wie Sie die Komponente verwenden (z. B. "INeedSomething", nicht "IDoSomething").

Worauf sich das Prinzip der Abhängigkeitsinversion nicht bezieht, ist die einfache Praxis, Abhängigkeiten mithilfe von Schnittstellen zu abstrahieren (z. B. MyService → [ILogger ⇐ Logger]). Dadurch wird eine Komponente vom spezifischen Implementierungsdetail der Abhängigkeit entkoppelt, die Beziehung zwischen dem Verbraucher und der Abhängigkeit wird jedoch nicht umgekehrt (z. B. [MyService → IMyServiceLogger] ⇐ Logger).

Die Bedeutung des Abhängigkeitsinversionsprinzips kann auf das singuläre Ziel reduziert werden, Softwarekomponenten, die für einen Teil ihrer Funktionalität auf externen Abhängigkeiten beruhen (Protokollierung, Validierung usw.), wiederverwenden zu können.

Innerhalb dieses allgemeinen Ziels der Wiederverwendung können wir zwei Untertypen der Wiederverwendung abgrenzen:

  1. Verwenden einer Softwarekomponente in mehreren Anwendungen mit Subabhängigkeitsimplementierungen (z. B. Sie haben einen DI-Container entwickelt und möchten die Protokollierung bereitstellen, möchten Ihren Container jedoch nicht an einen bestimmten Logger koppeln, sodass jeder, der Ihren Container verwendet, dies auch tun muss Verwenden Sie die von Ihnen gewählte Protokollierungsbibliothek.

  2. Verwenden von Softwarekomponenten in einem sich entwickelnden Kontext (z. B. Sie haben Geschäftslogikkomponenten entwickelt, die in mehreren Versionen einer Anwendung, in denen sich die Implementierungsdetails weiterentwickeln, gleich bleiben).

Beim ersten Fall der Wiederverwendung von Komponenten über mehrere Anwendungen hinweg, z. B. mit einer Infrastrukturbibliothek, besteht das Ziel darin, Ihren Verbrauchern einen zentralen Infrastrukturbedarf zu bieten, ohne Ihre Verbraucher an Unterabhängigkeiten Ihrer eigenen Bibliothek zu koppeln, da die Übernahme von Abhängigkeiten von solchen Abhängigkeiten Ihre Anforderungen erfordert Verbraucher müssen die gleichen Abhängigkeiten auch verlangen. Dies kann problematisch sein, wenn Benutzer Ihrer Bibliothek eine andere Bibliothek für dieselben Infrastrukturanforderungen verwenden (z. B. NLog vs. log4net) oder wenn sie eine spätere Version der erforderlichen Bibliothek verwenden, die nicht abwärtskompatibel mit der Version ist von Ihrer Bibliothek benötigt.

Beim zweiten Fall der Wiederverwendung von Business-Logic-Komponenten (dh "übergeordneten Komponenten") besteht das Ziel darin, die Kerndomänenimplementierung Ihrer Anwendung von den sich ändernden Anforderungen Ihrer Implementierungsdetails (dh Ändern / Aktualisieren von Persistenzbibliotheken, Messaging-Bibliotheken) zu isolieren , Verschlüsselungsstrategien usw.). Im Idealfall sollte das Ändern der Implementierungsdetails einer Anwendung die Komponenten, die die Geschäftslogik der Anwendung kapseln, nicht beschädigen.

Hinweis: Einige lehnen es möglicherweise ab, diesen zweiten Fall als tatsächliche Wiederverwendung zu beschreiben, da Komponenten wie Geschäftslogikkomponenten, die in einer einzelnen sich entwickelnden Anwendung verwendet werden, nur eine einzige Verwendung darstellen. Die Idee hier ist jedoch, dass jede Änderung an den Implementierungsdetails der Anwendung einen neuen Kontext und damit einen anderen Anwendungsfall ergibt, obwohl die endgültigen Ziele als Isolation vs. Portabilität unterschieden werden könnten.

Während das Befolgen des Abhängigkeitsinversionsprinzips in diesem zweiten Fall einige Vorteile bieten kann, sollte beachtet werden, dass sein Wert, der auf moderne Sprachen wie Java und C # angewendet wird, stark reduziert ist, möglicherweise bis zu dem Punkt, dass er irrelevant ist. Wie bereits erwähnt, werden beim DIP Implementierungsdetails vollständig in separate Pakete aufgeteilt. Im Fall einer sich entwickelnden Anwendung wird jedoch durch die einfache Verwendung von Schnittstellen, die in Bezug auf die Geschäftsdomäne definiert sind, verhindert, dass übergeordnete Komponenten aufgrund sich ändernder Anforderungen an Implementierungsdetailkomponenten geändert werden müssen, selbst wenn sich die Implementierungsdetails letztendlich im selben Paket befinden . Dieser Teil des Prinzips spiegelt Aspekte wider, die für die Sprache relevant waren, als das Prinzip kodifiziert wurde (dh C ++) und die für neuere Sprachen nicht relevant sind. Das gesagt,

Eine längere Diskussion dieses Prinzips in Bezug auf die einfache Verwendung von Schnittstellen, Abhängigkeitsinjektion und dem Muster der getrennten Schnittstelle finden Sie hier . Zusätzlich wird eine Diskussion darüber , wie bezieht sich das Prinzip dynamisch typisierten Sprachen wie JavaScript können gefunden werden hier .


17
Vielen Dank. Ich sehe jetzt, wie meine Antwort den Punkt verfehlt. Der Unterschied zwischen MyService → [ILogger ⇐ Logger] und [MyService → IMyServiceLogger] ⇐ Logger ist subtil, aber wichtig.
Patrick McElhaney

2
In der gleichen Zeile ist es hier sehr gut erklärt: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
Wie ich mir wünschte, die Leute würden aufhören, Logger als kanonischen Anwendungsfall für die Abhängigkeitsinjektion zu verwenden. Besonders in Verbindung mit log4net, wo es fast ein Anti-Pattern ist. Abgesehen davon, Kickass Erklärung!
Casper Leon Nielsen

4
@ Casper Leon Nielsen - DIP hat nichts mit DI zu tun. Sie sind weder Synonyme noch gleichwertige Konzepte.
TSmith

4
@ VF1 Wie im zusammenfassenden Absatz angegeben, liegt die Bedeutung des Abhängigkeitsinversionsprinzips hauptsächlich bei der Wiederverwendung. Wenn John eine Bibliothek freigibt, die eine externe Abhängigkeit von einer Protokollierungsbibliothek hat und Sam Johns Bibliothek verwenden möchte, übernimmt Sam die vorübergehende Protokollierungsabhängigkeit. Sam kann seine Anwendung niemals ohne die von John ausgewählte Protokollierungsbibliothek bereitstellen. Wenn John jedoch dem DIP folgt, kann Sam einen Adapter bereitstellen und die von ihm ausgewählte Protokollierungsbibliothek verwenden. Beim DIP geht es nicht um Bequemlichkeit, sondern um Kopplung.
Derek Greer

13

Wenn wir Softwareanwendungen entwerfen, können wir die Klassen auf niedriger Ebene als Klassen betrachten, die grundlegende und primäre Operationen implementieren (Festplattenzugriff, Netzwerkprotokolle, ...), und Klassen auf hoher Ebene die Klassen, die komplexe Logik (Geschäftsabläufe, ...) kapseln.

Die letzten stützen sich auf die unteren Klassen. Ein natürlicher Weg, solche Strukturen zu implementieren, wäre das Schreiben von Klassen auf niedriger Ebene und sobald wir sie haben, die komplexen Klassen auf hoher Ebene zu schreiben. Da hochrangige Klassen in Bezug auf andere definiert sind, scheint dies der logische Weg zu sein. Dies ist jedoch kein flexibles Design. Was passiert, wenn wir eine niedrige Klasse ersetzen müssen?

Das Abhängigkeitsinversionsprinzip besagt, dass:

  • High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beides sollte von Abstraktionen abhängen.
  • Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.

Dieses Prinzip versucht, die herkömmliche Vorstellung "umzukehren", dass High-Level-Module in Software von den Low-Level-Modulen abhängen sollten. Hier besitzen übergeordnete Module die Abstraktion (z. B. die Entscheidung über die Methoden der Schnittstelle), die von untergeordneten Modulen implementiert werden. Dadurch werden Module niedrigerer Ebene von Modulen höherer Ebene abhängig.


11

Die gut angewendete Abhängigkeitsinversion bietet Flexibilität und Stabilität auf der Ebene der gesamten Architektur Ihrer Anwendung. Dadurch kann sich Ihre Anwendung sicherer und stabiler entwickeln.

Traditionelle Schichtarchitektur

Traditionell hing eine Benutzeroberfläche mit geschichteter Architektur von der Geschäftsschicht ab, und dies hing wiederum von der Datenzugriffsschicht ab.

Sie müssen die Ebene, das Paket oder die Bibliothek verstehen. Mal sehen, wie der Code wäre.

Wir hätten eine Bibliothek oder ein Paket für die Datenzugriffsschicht.

// DataAccessLayer.dll
public class ProductDAO {

}

Und eine andere Geschäftslogik der Bibliotheks- oder Paketschicht, die von der Datenzugriffsschicht abhängt.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Schichtarchitektur mit Abhängigkeitsinversion

Die Abhängigkeitsinversion zeigt Folgendes an:

High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beides sollte von Abstraktionen abhängen.

Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.

Was sind die High-Level-Module und Low-Level-Module? Bei Modulen wie Bibliotheken oder Paketen handelt es sich bei Modulen auf hoher Ebene um Module, die traditionell Abhängigkeiten aufweisen und von denen sie auf niedriger Ebene abhängen.

Mit anderen Worten, auf hoher Ebene des Moduls wird die Aktion aufgerufen und auf niedriger Ebene wird die Aktion ausgeführt.

Eine vernünftige Schlussfolgerung aus diesem Prinzip ist, dass es keine Abhängigkeit zwischen Konkretionen geben sollte, sondern eine Abhängigkeit von einer Abstraktion. Aber nach dem Ansatz, den wir verfolgen, können wir die Abhängigkeit von Investitionen falsch anwenden, aber eine Abstraktion.

Stellen Sie sich vor, wir passen unseren Code wie folgt an:

Wir hätten eine Bibliothek oder ein Paket für die Datenzugriffsschicht, die die Abstraktion definieren.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Und eine andere Geschäftslogik der Bibliotheks- oder Paketschicht, die von der Datenzugriffsschicht abhängt.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Obwohl wir von einer Abstraktionsabhängigkeit abhängig sind, bleibt die Abhängigkeit zwischen Geschäft und Datenzugriff gleich.

Um eine Abhängigkeitsinversion zu erhalten, muss die Persistenzschnittstelle in dem Modul oder Paket definiert werden, in dem sich diese Logik oder Domäne auf hoher Ebene befindet, und nicht im Modul auf niedriger Ebene.

Definieren Sie zunächst, was die Domänenschicht ist, und die Abstraktion ihrer Kommunikation wird als Persistenz definiert.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Nachdem die Persistenzschicht von der Domäne abhängt, können Sie jetzt invertieren, wenn eine Abhängigkeit definiert ist.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(Quelle: xurxodev.com )

Das Prinzip vertiefen

Es ist wichtig, das Konzept gut zu verarbeiten und den Zweck und die Vorteile zu vertiefen. Wenn wir mechanisch bleiben und das typische Fall-Repository kennenlernen, können wir nicht identifizieren, wo wir das Prinzip der Abhängigkeit anwenden können.

Aber warum invertieren wir eine Abhängigkeit? Was ist das Hauptziel über spezifische Beispiele hinaus?

Dies ermöglicht üblicherweise, dass sich die stabilsten Dinge, die nicht von weniger stabilen Dingen abhängig sind, häufiger ändern.

Es ist einfacher, den Persistenztyp zu ändern, entweder die Datenbank oder die Technologie, um auf dieselbe Datenbank zuzugreifen, als die Domänenlogik oder Aktionen, die für die Kommunikation mit der Persistenz ausgelegt sind. Aus diesem Grund ist die Abhängigkeit umgekehrt, da es einfacher ist, die Persistenz zu ändern, wenn diese Änderung auftritt. Auf diese Weise müssen wir die Domain nicht ändern. Die Domänenschicht ist die stabilste von allen, weshalb sie von nichts abhängen sollte.

Es gibt jedoch nicht nur dieses Repository-Beispiel. Es gibt viele Szenarien, in denen dieses Prinzip gilt, und es gibt Architekturen, die auf diesem Prinzip basieren.

Architekturen

Es gibt Architekturen, bei denen die Abhängigkeitsinversion der Schlüssel zu ihrer Definition ist. In allen Domänen ist es das Wichtigste und es sind Abstraktionen, die angeben, dass das Kommunikationsprotokoll zwischen der Domäne und den übrigen Paketen oder Bibliotheken definiert ist.

Saubere Architektur

In der sauberen Architektur befindet sich die Domäne in der Mitte. Wenn Sie in Richtung der Pfeile schauen, die die Abhängigkeit anzeigen, ist klar, welche Ebenen am wichtigsten und stabilsten sind. Die äußeren Schichten gelten als instabile Werkzeuge. Vermeiden Sie daher, von ihnen abhängig zu sein.


(Quelle: 8thlight.com )

Sechseckige Architektur

Genauso verhält es sich mit der hexagonalen Architektur, bei der sich die Domäne ebenfalls im zentralen Teil befindet und Ports Abstraktionen der Kommunikation vom Domino nach außen sind. Auch hier ist offensichtlich, dass die Domäne am stabilsten ist und die traditionelle Abhängigkeit invertiert ist.


Ich bin mir nicht sicher, was es heißen soll , aber dieser Satz ist inkohärent: "Aber nach dem Ansatz, den wir verfolgen, können wir die Abhängigkeit von Investitionen falsch anwenden, aber eine Abstraktion." Vielleicht möchten Sie das Problem beheben?
Mark Amery

9

Für mich ist das im offiziellen Artikel beschriebene Prinzip der Abhängigkeitsinversion ein fehlgeleiteter Versuch, die Wiederverwendbarkeit von Modulen zu verbessern, die von Natur aus weniger wiederverwendbar sind, sowie eine Möglichkeit, ein Problem in der C ++ - Sprache zu umgehen.

Das Problem in C ++ ist, dass Header-Dateien normalerweise Deklarationen von privaten Feldern und Methoden enthalten. Wenn ein C ++ - Modul auf hoher Ebene die Header-Datei für ein Modul auf niedriger Ebene enthält, hängt dies von den tatsächlichen Implementierungsdetails dieses Moduls ab. Und das ist natürlich keine gute Sache. In den heute gebräuchlicheren modernen Sprachen ist dies jedoch kein Problem.

High-Level-Module sind von Natur aus weniger wiederverwendbar als Low-Level-Module, da erstere normalerweise anwendungs- / kontextspezifischer sind als letztere. Beispielsweise ist eine Komponente, die einen UI-Bildschirm implementiert, von höchster Ebene und auch sehr (vollständig?) Anwendungsspezifisch. Der Versuch, eine solche Komponente in einer anderen Anwendung wiederzuverwenden, ist kontraproduktiv und kann nur zu einer Überentwicklung führen.

Die Erstellung einer separaten Abstraktion auf derselben Ebene einer Komponente A, die von einer Komponente B abhängt (die nicht von A abhängt), kann daher nur durchgeführt werden, wenn Komponente A für die Wiederverwendung in verschiedenen Anwendungen oder Kontexten wirklich nützlich ist. Wenn dies nicht der Fall ist, wäre die Anwendung von DIP ein schlechtes Design.


Selbst wenn die Abstraktion auf hoher Ebene nur im Kontext dieser Anwendung nützlich ist, kann sie einen Wert haben, wenn Sie die tatsächliche Implementierung durch einen Stub zum Testen ersetzen möchten (oder eine Befehlszeilenschnittstelle für eine Anwendung bereitstellen möchten, die normalerweise im Web verfügbar ist)
Przemek Pokrywka

3
DIP ist sprachübergreifend wichtig und hat nichts mit C ++ selbst zu tun. Selbst wenn Ihr High-Level-Code Ihre Anwendung niemals verlässt, können Sie mit DIP Codeänderungen einschließen, wenn Sie Code auf niedrigerer Ebene hinzufügen oder ändern. Dies reduziert sowohl die Wartungskosten als auch die unbeabsichtigten Folgen von Änderungen. DIP ist ein übergeordnetes Konzept. Wenn Sie es nicht verstehen, müssen Sie mehr googeln.
Dirk Bester

3
Ich denke, Sie haben falsch verstanden, was ich über C ++ gesagt habe. Es war nur eine Motivation für DIP; zweifellos ist es allgemeiner als das. Beachten Sie, dass der offizielle Artikel über DIP deutlich macht, dass die zentrale Motivation darin bestand, die Wiederverwendung von Modulen auf hoher Ebene zu unterstützen, indem sie gegen Änderungen in Modulen auf niedriger Ebene immun gemacht wurden. Ohne dass eine Wiederverwendung erforderlich ist, ist es sehr wahrscheinlich, dass es sich um Overkill und Überentwicklung handelt. (Hast du es gelesen? Es spricht auch über das C ++ - Problem.)
Rogério

1
Übersehen Sie nicht die umgekehrte Anwendung von DIP? Das heißt, die übergeordneten Module implementieren Ihre Anwendung im Wesentlichen. Ihr Hauptanliegen ist es nicht, es wiederzuverwenden, sondern es weniger abhängig von der Implementierung von Modulen niedrigerer Ebene zu machen, damit die Aktualisierung, um mit zukünftigen Änderungen Schritt zu halten, Ihre Anwendung von den Folgen der Zeit und der neuen Technologie isoliert. Dies soll nicht das Ersetzen von WindowsOS erleichtern, sondern WindowsOS weniger von den Implementierungsdetails der FAT / HDD abhängig machen, damit die neueren NTFS / SSDs ohne oder mit geringen Auswirkungen an WindowsOS angeschlossen werden können.
knockNrod

1
Der UI-Bildschirm ist definitiv nicht das Modul der höchsten Ebene oder sollte für keine Anwendung geeignet sein. Module der höchsten Ebene enthalten Geschäftsregeln. Ein Modul der höchsten Ebene wird nicht durch sein Potenzial für die Wiederverwendbarkeit definiert. Bitte überprüfen Sie die einfache Erklärung von Onkel Bob in diesem Artikel: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

Grundsätzlich heißt es:

Die Klasse sollte von Abstraktionen (z. B. Schnittstelle, abstrakte Klassen) abhängen, nicht von bestimmten Details (Implementierungen).


Kann es so einfach sein? Es gibt lange Artikel und sogar Bücher, wie DerekGreer erwähnt hat? Eigentlich suchte ich nach einer einfachen Antwort, aber es ist unglaublich, wenn es so einfach ist: D
Darius.V

1
@ darius-v es ist keine andere Version von "Abstraktionen verwenden". Es geht darum, wem die Schnittstellen gehören. Der Principal sagt, dass Client (übergeordnete Komponenten) Schnittstellen definieren und untergeordnete Komponenten diese implementieren sollten.
Boran

6

Gute Antworten und gute Beispiele geben hier bereits andere.

Der Grund, warum DIP wichtig ist, liegt darin, dass es das OO-Prinzip "lose gekoppeltes Design" gewährleistet.

Die Objekte in Ihrer Software sollten NICHT in eine Hierarchie geraten, in der einige Objekte die Objekte der obersten Ebene sind, abhängig von Objekten der unteren Ebene. Änderungen an Objekten auf niedriger Ebene werden dann auf Ihre Objekte auf oberster Ebene übertragen, wodurch die Software für Änderungen sehr anfällig ist.

Sie möchten, dass Ihre Objekte der obersten Ebene sehr stabil und nicht anfällig für Änderungen sind. Daher müssen Sie die Abhängigkeiten invertieren.


6
Wie genau macht DIP das für Java- oder .NET-Code? In diesen Sprachen erfordern Änderungen an der Implementierung eines Low-Level-Moduls im Gegensatz zu C ++ keine Änderungen an den High-Level-Modulen, die es verwenden. Nur Änderungen an der öffentlichen Schnittstelle würden sich durchsetzen, aber dann müsste sich auch die auf der höheren Ebene definierte Abstraktion ändern.
Rogério

5

Eine viel klarere Möglichkeit, das Prinzip der Abhängigkeitsinversion anzugeben, ist:

Ihre Module, die komplexe Geschäftslogik kapseln, sollten nicht direkt von anderen Modulen abhängen, die Geschäftslogik kapseln. Stattdessen sollten sie nur von Schnittstellen zu einfachen Daten abhängen.

Das heißt, anstatt Ihre Klasse Logicwie gewöhnlich zu implementieren :

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

Sie sollten etwas tun wie:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Dataund DataFromDependencysollte im selben Modul leben wie Logic, nicht mit Dependency.

Warum das?

  1. Die beiden Geschäftslogikmodule sind nun entkoppelt. Bei DependencyÄnderungen müssen Sie nicht ändern Logic.
  2. Zu verstehen, was Logicfunktioniert, ist eine viel einfachere Aufgabe: Es funktioniert nur mit einem ADT.
  3. Logickann jetzt einfacher getestet werden. Sie können jetzt direkt Datamit gefälschten Daten instanziieren und diese weitergeben. Keine Notwendigkeit für Verspottungen oder komplexe Testgerüste.

Ich denke nicht, dass das richtig ist. Befindet DataFromDependencysich das DependencyModul , auf das direkt verwiesen wird , im selben Modul wie Logic, Logichängt das DependencyModul zur Kompilierungszeit immer noch direkt vom Modul ab. Laut Onkel Bobs Erklärung des Prinzips ist es der springende Punkt von DIP, dies zu vermeiden. Um DIP zu folgen, Datasollte es sich im selben Modul wie Logic, aber DataFromDependencyim selben Modul wie befinden Dependency.
Mark Amery

3

Inversion of Control (IoC) ist ein Entwurfsmuster, bei dem einem Objekt seine Abhängigkeit von einem externen Framework übergeben wird, anstatt ein Framework nach seiner Abhängigkeit zu fragen.

Pseudocode-Beispiel mit traditioneller Suche:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Ähnlicher Code mit IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Die Vorteile von IoC sind:

  • Sie sind nicht von einem zentralen Framework abhängig, daher kann dies bei Bedarf geändert werden.
  • Da Objekte durch Injektion erstellt werden, vorzugsweise über Schnittstellen, ist es einfach, Komponententests zu erstellen, die Abhängigkeiten durch Scheinversionen ersetzen.
  • Code entkoppeln.

Ist das Prinzip der Abhängigkeitsinversion und die Inversion der Kontrolle dasselbe?
Peter Mortensen

3
Die "Inversion" in DIP ist nicht dieselbe wie in Inversion of Control. Bei der ersten geht es um Abhängigkeiten zur Kompilierungszeit, bei der zweiten um den Kontrollfluss zwischen Methoden zur Laufzeit.
Rogério

Ich habe das Gefühl, dass der IoC-Version etwas fehlt. Wie wird welches Datenbankobjekt definiert, woher kommt es? Ich versuche zu verstehen, wie dies nicht nur die Angabe aller Abhängigkeiten auf der obersten Ebene in einer riesigen Gottabhängigkeit verzögert
Richard Tingle

1
Wie bereits von @ Rogério gesagt, ist DIP nicht DI / IoC. Diese Antwort ist falsch IMO
zeraDev

1

Der Punkt der Abhängigkeitsinversion besteht darin, wiederverwendbare Software zu erstellen.

Die Idee ist, dass anstelle von zwei aufeinander stehenden Codeteilen eine abstrahierte Schnittstelle verwendet wird. Dann können Sie jedes Stück ohne das andere wiederverwenden.

Dies wird am häufigsten durch einen IoC-Container (Inversion of Control) wie Spring in Java erreicht. In diesem Modell werden Eigenschaften von Objekten über eine XML-Konfiguration eingerichtet, anstatt dass die Objekte ausgehen und ihre Abhängigkeit ermitteln.

Stellen Sie sich diesen Pseudocode vor ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass hängt direkt sowohl von der Service-Klasse als auch von der ServiceLocator-Klasse ab. Es benötigt beide, wenn Sie es in einer anderen Anwendung verwenden möchten. Stellen Sie sich das vor ...

public class MyClass
{
  public IService myService;
}

Jetzt basiert MyClass auf einer einzigen Schnittstelle, der IService-Schnittstelle. Wir würden den IoC-Container tatsächlich den Wert dieser Variablen festlegen lassen.

So kann MyClass jetzt problemlos in anderen Projekten wiederverwendet werden, ohne die Abhängigkeit dieser beiden anderen Klassen mit sich zu bringen.

Noch besser ist, dass Sie die Abhängigkeiten von MyService und die Abhängigkeiten dieser Abhängigkeiten nicht ziehen müssen, und ... nun, Sie haben die Idee.


2
Bei dieser Antwort geht es wirklich nicht um DIP, sondern um etwas anderes. Zwei Codeteile können sich auf eine abstrahierte Schnittstelle stützen und nicht aufeinander und folgen immer noch nicht dem Prinzip der Abhängigkeitsinversion.
Rogério

1

Wenn wir davon ausgehen können, dass ein "hochrangiger" Mitarbeiter eines Unternehmens für die Ausführung seiner Pläne bezahlt wird und dass diese Pläne durch die Gesamtausführung vieler Pläne eines "niedrigen" Mitarbeiters geliefert werden, dann könnten wir sagen Es ist im Allgemeinen ein schrecklicher Plan, wenn die Planbeschreibung des hochrangigen Mitarbeiters in irgendeiner Weise an den spezifischen Plan eines untergeordneten Mitarbeiters gekoppelt ist.

Wenn eine hochrangige Führungskraft einen Plan zur "Verbesserung der Lieferzeit" hat und angibt, dass ein Mitarbeiter in der Reederei jeden Morgen Kaffee trinken und sich dehnen muss, ist dieser Plan eng gekoppelt und weist einen geringen Zusammenhalt auf. Wenn der Plan jedoch keinen bestimmten Mitarbeiter erwähnt und lediglich erfordert, dass "eine Einheit, die Arbeiten ausführen kann, bereit ist zu arbeiten", ist der Plan lose gekoppelt und kohärenter: Die Pläne überschneiden sich nicht und können leicht ersetzt werden . Auftragnehmer oder Roboter können die Mitarbeiter problemlos ersetzen, und der Plan der hohen Ebene bleibt unverändert.

"High Level" im Abhängigkeitsinversionsprinzip bedeutet "wichtiger".


1
Dies ist eine bemerkenswert gute Analogie. So wie nach dem DIC Komponenten auf hoher Ebene zur Laufzeit immer noch von Komponenten auf niedriger Ebene abhängen, hängt in dieser Analogie die Ausführung der Pläne auf hoher Ebene von den Plänen auf niedriger Ebene ab. Aber genau wie unter DIC sind es die Low-Level-Pläne, die zur Kompilierungszeit von den High-Level-Plänen abhängen , ist es in Ihrer Analogie der Fall, dass die Bildung der Low-Level-Pläne von den High-Level-Plänen abhängt.
Mark Amery

0

Ich kann sehen, dass in den obigen Antworten eine gute Erklärung gegeben wurde. Ich möchte jedoch eine einfache Erklärung mit einem einfachen Beispiel geben.

Das Prinzip der Abhängigkeitsinversion ermöglicht es dem Programmierer, die fest codierten Abhängigkeiten zu entfernen, so dass die Anwendung lose gekoppelt und erweiterbar wird.

Wie man das erreicht: durch Abstraktion

Ohne Abhängigkeitsinversion:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

Im obigen Code-Snippet ist das Adressobjekt fest codiert. Stattdessen, wenn wir die Abhängigkeitsinversion verwenden und das Adressobjekt durch Übergabe der Konstruktor- oder Setter-Methode einfügen können. Mal schauen.

Mit Abhängigkeitsinversion:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

Abhängigkeitsinversion: Abhängig von Abstraktionen, nicht von Konkretionen.

Umkehrung der Kontrolle: Main vs Abstraction und wie der Main der Klebstoff der Systeme ist.

DIP und IoC

Dies sind einige gute Beiträge, die darüber sprechen:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

Nehmen wir an, wir haben zwei Klassen: Engineerund Programmer:

Class Engineer ist abhängig von der Programmer-Klasse.

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

In diesem Beispiel ist die Klasse Engineervon unserer ProgrammerKlasse abhängig . Was passiert, wenn ich das ändern muss Programmer?

Natürlich muss ich das auch ändern Engineer. (Wow, an dieser Stelle OCPwird auch verletzt)

Was müssen wir dann tun, um dieses Chaos zu beseitigen? Die Antwort ist eigentlich Abstraktion. Durch Abstraktion können wir die Abhängigkeit zwischen diesen beiden Klassen entfernen. Zum Beispiel kann ich eine Interfacefür die Programmer-Klasse erstellen, und von nun an muss jede Klasse, die die verwenden möchte Programmer, ihre verwenden Interface. Durch Ändern der Programmer-Klasse müssen wir dann keine Klassen ändern, die sie verwendet haben. Aufgrund der Abstraktion, die wir verwenden gebraucht.

Hinweis: DependencyInjectionKann uns dabei helfen DIPund SRPauch.


-1

Zusätzlich zu den vielen allgemein guten Antworten möchte ich eine kleine eigene Stichprobe hinzufügen, um gute und schlechte Praktiken zu demonstrieren. Und ja, ich bin nicht einer, der Steine ​​wirft!

Angenommen, Sie möchten, dass ein kleines Programm einen String über die Konsolen-E / A in das Base64-Format konvertiert . Hier ist der naive Ansatz:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

Das DIP besagt grundsätzlich, dass Komponenten auf hoher Ebene nicht von einer Implementierung auf niedriger Ebene abhängig sein sollten, wobei "Ebene" die Entfernung von E / A gemäß Robert C. Martin ("Saubere Architektur") ist. Aber wie kommst du aus dieser Situation heraus? Einfach indem Sie den zentralen Encoder nur von Schnittstellen abhängig machen, ohne sich darum zu kümmern, wie diese implementiert werden:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Beachten Sie, dass Sie nicht berühren GoodEncodermüssen, um den E / A-Modus zu ändern. Diese Klasse ist mit den ihr bekannten E / A-Schnittstellen zufrieden. jede Low-Level-Implementierung von IReadableund IWriteablewird es nie stören.


Ich denke nicht, dass dies Dependency Inversion ist. Schließlich haben Sie hier keine Abhängigkeit umgekehrt . Ihr Leser und Verfasser hängt GoodEncoderin Ihrem zweiten Beispiel nicht davon ab . Um ein DIP-Beispiel zu erstellen, müssen Sie eine Vorstellung davon einführen, was die hier extrahierten Schnittstellen "besitzt" - und sie insbesondere in dasselbe Paket wie den GoodEncoder einfügen, während ihre Implementierungen außerhalb bleiben.
Mark Amery

-2

Das Dependency Inversion Principle (DIP) besagt dies

i) High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beides sollte von Abstraktionen abhängen.

ii) Abstraktionen sollten niemals von Details abhängen. Details sollten von Abstraktionen abhängen.

Beispiel:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Hinweis: Die Klasse sollte von Abstraktionen wie Schnittstelle oder abstrakten Klassen abhängen, nicht von bestimmten Details (Implementierung der Schnittstelle).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.