it-swarm-eu.dev

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

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

163
Phillip Wells

Überprüfen Sie dieses Dokument: Das Prinzip der Abhängigkeitsinversion .

Im Wesentlichen 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.

Warum ist es wichtig, kurz gesagt: Änderungen sind riskant, und durch die Abhängigkeit eines Konzepts anstelle einer Implementierung wird der Bedarf an Änderungen an den Anrufstandorten reduziert.

Der DIP reduziert effektiv die Kopplung zwischen verschiedenen Codeteilen. Die Idee ist, dass, obwohl es viele Möglichkeiten gibt, eine Protokollierungsfunktion zu implementieren, die Art, wie Sie sie verwenden würden, 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 Anrufsites sollten weniger von Änderungen betroffen sein, die Sie beim Aufrechterhalten oder Erweitern des Protokollierungsmechanismus vornehmen könnten.

Indem Sie die Implementierung auch von einer Schnittstelle abhängig machen, haben Sie die Möglichkeit, zur Laufzeit auszuwählen, welche Implementierung für Ihre bestimmte Umgebung besser geeignet ist. Abhängig von den Fällen kann dies auch interessant sein.

102
Carl Seleborg

Die Bücher Agile Softwareentwicklung, Prinzipien, Muster und Vorgehensweisen sowie Agile Prinzipien, Muster und Vorgehensweisen in C # sind die besten Ressourcen, um die ursprünglichen Ziele und Motivationen des Abhängigkeitsinversionsprinzips vollständig zu verstehen. Der Artikel "The Dependency Inversion Principle" ist ebenfalls eine gute Ressource. Da es sich jedoch um eine komprimierte Version eines Entwurfs handelt, der schließlich in die oben genannten Bücher eingegangen ist, werden wichtige Diskussionen über das Konzept von a ausgelassen Paket- und Schnittstellenbesitz, die der Schlüssel zur Unterscheidung dieses Prinzips von der allgemeineren Empfehlung sind, "eine Schnittstelle zu programmieren, keine Implementierung", die in dem Buch Design Patterns (Gamma, et al.) zu finden ist.

Um eine Zusammenfassung zu liefern, geht es beim Abhängigkeitsinversionsprinzip in erster Linie um Umkehren die herkömmliche Richtung von Abhängigkeiten von "Komponenten höherer Ebene" zu "Komponenten niedrigerer Ebene", so dass es sich um "Komponenten niedrigerer Ebene" handelt abhängig von den Schnittstellen im Besitz der "übergeordneten" Komponenten. (Hinweis: "übergeordnete" Komponente bezieht sich hier auf die Komponente, die externe Abhängigkeiten/Dienste benötigt, nicht unbedingt ihre konzeptionelle Position innerhalb einer Schichtenarchitektur.) Dabei ist die Kopplung nicht reduziert so viel wie es ist verschoben von Komponenten, die theoretisch weniger wertvoll sind, zu Komponenten, die theoretisch mehr wert sind.

Dies wird erreicht, indem Komponenten entworfen werden, deren externe Abhängigkeiten in Form einer Schnittstelle ausgedrückt werden, für die eine Implementierung vom Verbraucher der Komponente bereitgestellt werden 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 durch die Verwendung von Schnittstellen zu abstrahieren (z. B. MyService → [ILogger ⇐ Logger]). Während dies eine Komponente von dem spezifischen Implementierungsdetail der Abhängigkeit entkoppelt, invertiert es nicht die Beziehung zwischen dem Verbraucher und der Abhängigkeit (z. B. [MyService → IMyServiceLogger]] Logger.

Die Bedeutung des Abhängigkeitsinversionsprinzips lässt sich auf ein einzigartiges Ziel zurückführen, nämlich die Wiederverwendung von Softwarekomponenten, die für einen Teil ihrer Funktionalität (Protokollierung, Validierung usw.) auf externe Abhängigkeiten angewiesen sind.

Innerhalb dieses allgemeinen Ziels der Wiederverwendung können zwei Unterarten der Wiederverwendung abgegrenzt werden:

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

  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 ändern, gleich bleiben).

Beim ersten Fall der Wiederverwendung von Komponenten in mehreren Anwendungen, z. B. in einer Infrastrukturbibliothek, besteht das Ziel darin, Ihren Kunden eine grundlegende Infrastrukturanforderung bereitzustellen, ohne sie an Unterabhängigkeiten Ihrer eigenen Bibliothek zu koppeln, da das Eingehen von Abhängigkeiten von solchen Abhängigkeiten Ihre Anforderungen erfüllt Verbraucher auch die gleichen Abhängigkeiten zu fordern. Dies kann problematisch sein, wenn Benutzer Ihrer Bibliothek eine andere Bibliothek für die gleichen Infrastrukturanforderungen verwenden (z. B. NLog oder log4net) oder eine spätere Version der erforderlichen Bibliothek verwenden, die nicht mit der Version abwärtskompatibel ist von Ihrer Bibliothek benötigt.

Beim zweiten Fall der Wiederverwendung von Geschäftslogikkomponenten (dh "übergeordneten Komponenten") besteht das Ziel darin, die Kerndomänenimplementierung Ihrer Anwendung von den sich ändernden Anforderungen Ihrer Implementierungsdetails (dh Ändern/Aktualisieren von Persistenzbibliotheken, Messagingbibliotheken) zu isolieren , Verschlüsselungsstrategien usw.). Wenn Sie die Implementierungsdetails einer Anwendung ändern, sollten Sie die Komponenten, die die Geschäftslogik der Anwendung enthalten, im Idealfall nicht beschädigen.

Hinweis: Einige lehnen es möglicherweise ab, diesen zweiten Fall als tatsächliche Wiederverwendung zu bezeichnen, da Komponenten wie Geschäftslogikkomponenten, die in einer einzelnen sich entwickelnden Anwendung verwendet werden, nur eine einzige Verwendung darstellen. Die Idee dabei ist jedoch, dass jede Änderung der 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.

Obwohl das Befolgen des Abhängigkeitsinversionsprinzips in diesem zweiten Fall einige Vorteile bieten kann, sollte beachtet werden, dass sein Wert für moderne Sprachen wie Java und C # stark reduziert ist, möglicherweise bis zu dem Punkt, dass er irrelevant ist. Wie bereits erwähnt, werden beim DIP die Implementierungsdetails vollständig in separate Pakete aufgeteilt. Im Fall einer sich entwickelnden Anwendung wird jedoch durch die einfache Verwendung von Schnittstellen, die im Hinblick auf den Geschäftsbereich definiert wurden, verhindert, dass übergeordnete Komponenten aufgrund sich ändernder Anforderungen an Implementierungsdetailkomponenten geändert werden müssen, selbst wenn sich die Implementierungsdetails letztendlich innerhalb desselben Pakets befinden . Dieser Teil des Prinzips spiegelt Aspekte wider, die für die jeweilige Sprache bei der Kodifizierung des Prinzips relevant waren (d. H. C++) und für neuere Sprachen nicht relevant sind. Die Bedeutung des Dependency-Inversion-Prinzips liegt jedoch in erster Linie in der Entwicklung wiederverwendbarer Softwarekomponenten/-bibliotheken.

Eine längere Erörterung dieses Prinzips im Zusammenhang mit der einfachen Verwendung von Schnittstellen, der Abhängigkeitsinjektion und dem getrennten Schnittstellenmuster findet sich hier . Zusätzlich kann eine Diskussion darüber, wie sich das Prinzip auf dynamisch typisierte Sprachen wie JavaScript bezieht, gegeben werden hier .

137
Derek Greer

Wenn wir Softwareanwendungen entwerfen, können wir die untergeordneten Klassen als Klassen betrachten, die grundlegende und primäre Operationen (Plattenzugriff, Netzwerkprotokolle, ...) und Klassen der höheren Ebenen als Klassen für komplexe Logik (Geschäftsabläufe, ...) implementieren.

Die letzten setzen auf die Low-Level-Klassen. Ein natürlicher Weg, solche Strukturen zu implementieren, wäre das Schreiben von Low-Level-Klassen und, sobald wir sie haben, die komplexen High-Level-Klassen. Da übergeordnete Klassen von anderen definiert werden, scheint dies der logische Weg zu sein. Dies ist jedoch kein flexibles Design. Was passiert, wenn wir eine Low-Level-Klasse ersetzen müssen?

Das Prinzip der Inversion der Abhängigkeit lautet:

  • 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 soll die herkömmliche Ansicht "invertieren", dass High-Level-Module in Software von den Lower-Level-Modulen abhängen sollten. Hier besitzen High-Level-Module die Abstraktion (die zum Beispiel die Methoden der Schnittstelle bestimmt), die von Lower-Level-Modulen implementiert werden. Dadurch werden Module der unteren Ebene von Modulen der höheren Ebene abhängig gemacht.

10
nikhil.singhal

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

Traditionelle geschichtete Architektur

Traditionell hing eine geschichtete Architektur-Benutzeroberfläche von der Business-Schicht ab, und diese wiederum war von der Datenzugriffsebene abhängig.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Sie müssen Layer, Paket oder Bibliothek verstehen. Mal sehen, wie der Code aussehen würde.

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

// DataAccessLayer.dll
public class ProductDAO {

}

Und eine andere Bibliotheks- oder Paketschicht-Geschäftslogik, 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? Denkmodule wie Bibliotheken oder Pakete oder High-Level-Module wären solche, die traditionell Abhängigkeiten haben und auf niedriger Ebene von denen sie abhängen.

Mit anderen Worten, der Modul-High-Level wäre dort, wo die Aktion aufgerufen wird, und der Low-Level, wo die Aktion ausgeführt wird.

Eine vernünftige Schlussfolgerung aus diesem Prinzip ist, dass zwischen Konkretionen keine Abhängigkeit bestehen darf, sondern dass eine Abhängigkeit von einer Abstraktion bestehen muss. Aber gemäß unserem Ansatz können wir die Abhängigkeit von der 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 Bibliotheks- oder Paketschicht-Geschäftslogik, 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 Unternehmen und Datenzugriff gleich.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Um eine Abhängigkeitsinversion zu erhalten, muss die Persistenzschnittstelle in dem Modul oder Paket definiert werden, in dem sich diese übergeordnete Logik oder Domäne befindet, und nicht in dem untergeordneten Modul.

Definieren Sie zuerst, 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ängig ist, können Sie jetzt invertieren, wenn eine Abhängigkeit definiert ist.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Das Prinzip vertiefen

Es ist wichtig, das Konzept gut zu verstehen und den Zweck und den Nutzen zu vertiefen. Wenn wir mechanisch bleiben und das typische Fallarchiv kennenlernen, können wir nicht feststellen, wo wir das Prinzip der Abhängigkeit anwenden können.

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

In der Regel erlaubt es, die stabilsten Dinge, die nicht von weniger stabilen Dingen abhängig sind, häufiger zu ändern.

Es ist einfacher, den Persistenztyp zu ändern, entweder die Datenbank oder Technologie, um auf dieselbe Datenbank zuzugreifen, wie die Domänenlogik oder Aktionen, die für die Kommunikation mit Persistenz entwickelt wurden. Aus diesem Grund ist die Abhängigkeit umgekehrt, da die Persistenz bei einer Änderung leichter geändert werden kann. Auf diese Weise müssen wir die Domäne nicht ändern. Die Domänenschicht ist die stabilste von allen, weshalb sie nicht von irgendetwas abhängig sein 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 Inversion der Abhängigkeit der Schlüssel zu ihrer Definition ist. In allen Domänen ist dies am wichtigsten, und es sind Abstraktionen, die angeben, dass das Kommunikationsprotokoll zwischen der Domäne und dem Rest der Pakete oder Bibliotheken definiert ist.

Saubere Architektur

In Clean-Architektur Die Domäne befindet sich in der Mitte. Wenn Sie in Richtung der Pfeile schauen, die auf Abhängigkeit hinweisen, wird deutlich, welche die wichtigsten und stabilsten Schichten sind. Die äußeren Schichten werden als instabile Werkzeuge betrachtet.

Sechseckige Architektur

Auf dieselbe Weise geschieht dies bei der hexagonalen Architektur, bei der sich die Domäne auch im zentralen Teil befindet und die Ports Abstraktionen der Kommunikation vom Domino nach außen sind. Hier zeigt sich wiederum, dass die Domäne am stabilsten ist und die traditionelle Abhängigkeit umgekehrt ist.

9
xurxodev

Für mich ist das Prinzip der Inversion der Abhängigkeit, wie es im offiziellen Artikel beschrieben wird, ein irreführender Versuch, die Wiederverwendbarkeit von Modulen zu erhöhen, die von Natur aus weniger wiederverwendbar sind, als auch eine Möglichkeit, ein Problem in C++ zu umgehen Sprache.

Das Problem in C++ ist, dass Header-Dateien normalerweise Deklarationen von privaten Feldern und Methoden enthalten. Wenn ein übergeordnetes C++ - Modul die Header-Datei für ein untergeordnetes Modul enthält, hängt es daher von der tatsächlichen Datei ab implementierung Details dieses Moduls. Und das ist offensichtlich keine gute Sache. Dies ist jedoch in den heute gebräuchlicheren modernen Sprachen kein Thema.

High-Level-Module sind von Natur aus weniger wiederverwendbar als Low-Level-Module, da die ersteren normalerweise anwendungs-/kontextspezifischer sind als die letzteren. Beispielsweise ist eine Komponente, die einen UI-Bildschirm implementiert, auf 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ängig ist) kann nur durchgeführt werden, wenn Komponente A wirklich für die Wiederverwendung in verschiedenen Anwendungen oder Kontexten nützlich ist. Wenn dies nicht der Fall ist, wäre die Anwendung von DIP ein schlechtes Design.

9
Rogério

Grundsätzlich heißt es:

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

8
martin.ra

Eine viel klarere Art, das Prinzip der Inversion der Abhängigkeit zu erklären, ist:

Ihre Module, die komplexe Geschäftslogik kapseln, sollten nicht direkt von anderen Modulen abhängen, die Geschäftslogik kapseln. Sie sollten stattdessen nur auf Schnittstellen zu einfachen Daten angewiesen sein.

D. h., Anstatt Ihre Klasse Logic zu implementieren, wie es normalerweise Leute tun:

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
    }
}

Data und DataFromDependency sollten im selben Modul wie Logic leben, nicht mit Dependency.

Warum das machen?

  1. Die beiden Geschäftslogikmodule sind jetzt entkoppelt. Wenn sich Dependency ändert, müssen Sie Logic nicht ändern. 
  2. Zu verstehen, was Logic macht, ist eine viel einfachere Aufgabe: Sie arbeitet nur mit dem, was wie ein ADT aussieht.
  3. Logic kann jetzt einfacher getestet werden. Sie können jetzt Data direkt mit gefälschten Daten instanziieren und diese weiterleiten. Es sind keine Mocks oder komplexe Testgerüste erforderlich.
5
mattvonb

Gute Antworten und gute Beispiele werden hier bereits von anderen gegeben.

Der Grund DIP ist wichtig, weil er das OO-Prinzip "lose gekoppelte Konstruktion" gewährleistet.

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

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

5
Hace

Inversion of Control (IoC) ist ein Entwurfsmuster, bei dem einem Objekt seine Abhängigkeit von einem äußeren Rahmen übergeben wird, anstatt einen Rahmen für seine Abhängigkeit anzufordern.

Pseudocode-Beispiel mit herkömmlicher Suche:

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

Ähnliche Code mit IoC:

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

Die Vorteile von IoC sind:

  • Sie haben keine Abhängigkeit von einem zentralen Framework Dies kann jedoch geändert werden, wenn Gewünscht ist.
  • Da Objekte erstellt werden durch Injektion, vorzugsweise unter Verwendung von Schnittstellen, ist es einfach, Unit Tests zu erstellen, die Abhängigkeiten durch Scheinversionen ersetzen.
  • Code entkoppeln.
5
Staale

Der Sinn der Abhängigkeitsinversion besteht darin, wieder verwendbare Software zu erstellen.

Die Idee ist, dass anstelle von zwei Codeteilen, die aufeinander angewiesen sind, sie auf eine abstrahierte Schnittstelle angewiesen sind. Dann können Sie eines der beiden Teile ohne das andere wiederverwenden.

Am häufigsten wird dies durch eine Invertierung des IoC-Containers (Spring in Control) wie Spring in Java erreicht. In diesem Modell werden Eigenschaften von Objekten über eine XML-Konfiguration festgelegt, anstatt dass die Objekte ausgehen und ihre Abhängigkeit feststellen.

Stellen Sie sich diesen Pseudocode vor ...

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

MyClass hängt direkt von der Service-Klasse und der ServiceLocator-Klasse ab. Sie benötigen beide, wenn Sie sie in einer anderen Anwendung verwenden möchten. Nun stell dir das vor ...

public class MyClass
{
  public IService myService;
}

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

Daher kann MyClass jetzt problemlos in anderen Projekten verwendet werden, ohne dass die Abhängigkeit dieser beiden anderen Klassen mit einbezogen wird.

Noch besser, Sie müssen nicht die Abhängigkeiten von MyService und die Abhängigkeiten dieser Abhängigkeiten und die ... ziehen. Nun, Sie bekommen die Idee.

1
Marc Hughes

Inversion von Kontrollbehältern und Abhängigkeitsinjektionsmuster von Martin Fowler ist auch eine gute Lektüre. Ich fand Head First Design Patterns ein fantastisches Buch für meinen ersten Vorstoß in das Lernen von DI und anderen Mustern.

1
Chris Canal

Ich denke, ich habe ein viel besseres (intuitiveres) Beispiel.

  • Stellen Sie sich ein System (Webapp) mit Mitarbeiter- und Kontaktverwaltung (zwei Bildschirme) vor.
  • Sie sind nicht genau miteinander verwandt, also möchten Sie sie jeweils in einem eigenen Modul/Ordner

Sie hätten also einen "Haupteinstiegspunkt", der über das Employee-Managementmodul und das Contact-Managementmodul Bescheid wissen müsste, und es müsste Links in der Navigation zur Verfügung gestellt und API-Anforderungen akzeptiert werden. In anderen Worten: das Das Hauptmodul würde von diesen Zweien abhängen - es würde über ihre Controller, Routen und Links Bescheid wissen, die in der (gemeinsamen) Navigation dargestellt werden müssen.

Node.js Beispiel

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Bitte beachten Sie es gibt Fälle (einfache Fälle), in denen dies völlig in Ordnung ist.


Im Laufe der Zeit wird es so weit kommen, dass es nicht so einfach ist, neue Module hinzuzufügen. Sie müssen sich daran erinnern, API, Navigation, vielleicht permissions zu registrieren, und dieses main.js wird immer größer.

Und hier kommt die Abhängigkeitsinversion ins Spiel. Anstatt Ihr Hauptmodul von allen anderen Modulen abhängig zu machen, werden Sie einen "Kern" einführen und jedes Modul selbst registrieren lassen.

In diesem Fall geht es also darum, eine Vorstellung von ApplicationModule zu haben, die sich bei vielen Diensten anmelden kann (Routen, Navigation, Berechtigungen) und das Hauptmodul kann einfach bleiben (einfach Importmodul und Installation).

Mit anderen Worten, es geht darum, eine steckbare Architektur zu schaffen. Dies ist zusätzliche Arbeit und Code, den Sie schreiben, lesen und pflegen müssen, damit Sie dies nicht im Vorfeld tun sollten, sondern eher, wenn Sie diese Art von Geruch haben.

Interessant ist vor allem, dass Sie alles zu einem Plugin machen können, sogar die Persistenzschicht - was sich möglicherweise lohnt, wenn Sie viele Persistenzimplementierungen unterstützen müssen. Dies ist jedoch normalerweise nicht der Fall. Sehen Sie sich die andere Antwort für ein Bild mit hexagonaler Architektur an, es ist großartig für die Illustration - es gibt einen Kern und alles andere ist im Wesentlichen ein Plugin.

0
Kamil Tomšík

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

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

DIP and 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/

Neben den allgemein guten Antworten möchte ich eine kleine Auswahl meiner eigenen hinzufügen, um die guten und die schlechten Praktiken zu demonstrieren. Und ja, ich werfe keine Steine!

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

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-lever 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 mit hohem Hebel nicht von einer Implementierung auf niedriger Ebene abhängig sein sollten, wobei "Ebene" der Abstand von E/A gemäß Robert C. Martin ("Saubere Architektur") ist. Aber wie kommt man 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 GoodEncoder nicht berühren müssen, um den E/A-Modus zu ändern. Diese Klasse ist mit den ihm bekannten E/A-Schnittstellen zufrieden. Eine einfache Implementierung von IReadable und IWriteable wird das nie stören.

0
John Silence

Neben anderen Antworten ....

Lassen Sie mich zuerst ein Beispiel vorstellen ..

Lass es ein Hotel geben, das einen Lebensmittelgenerator nach seinen Vorräten fragt Das Hotel gibt dem Nahrungsmittelgenerator den Namen des Essens (z. B. Hühnchen) und der Generator gibt das angeforderte Essen an das Hotel zurück. Das Hotel kümmert sich jedoch nicht um die Art der Speisen, die es erhält und serviert. So liefert der Generator die Lebensmittel mit einem Label "Food" an das Hotel.

Diese Implementierung ist in Java

FactoryClass mit einer Factory-Methode. Der Nahrungsmittelgenerator

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Eine Abstract/Interface-Klasse

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


Chicken implementiert das Essen (eine konkrete Klasse)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Fisch implementiert die Nahrung (eine konkrete Klasse)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Endlich

Das Hotel

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Wie Sie sehen konnten, weiß das Hotel nicht, ob es sich um ein Hühnerobjekt oder um ein Fischobjekt handelt. Es weiß nur, dass es sich um ein Lebensmittelobjekt handelt, dh das Hotel hängt von der Lebensmittelklasse ab.

Sie werden auch feststellen, dass die Fish and Chicken-Klasse die Food-Klasse implementiert und sich nicht direkt auf das Hotel bezieht. Hühnchen und Fisch hängen ebenfalls von der Lebensmittelklasse ab.

Was bedeutet, dass sowohl die High-Level-Komponente (Hotel) als auch die Low-Level-Komponente (Fish and Chicken) von einer Abstraktion (Food) abhängen.

Dies wird als Abhängigkeitsinversion bezeichnet. 

0
Revolver

Das Prinzip der Inversion der Abhängigkeit (DIP) sagt das aus 

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();
        }
    }

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

0
Rejwanul Reja