Abhängigkeitsumkehr

Verfügbar

9.1 Abhängigkeitsumkehr

Erinnern Sie sich, wir haben einmal gesagt, dass man in einer Serveranwendung nicht einfach Streams erstellen kann new Thread().start()? Nur der Container sollte Threads erstellen. Wir werden diese Idee nun noch weiterentwickeln.

Alle Objekte sollten auch nur vom Container erstellt werden . Dabei handelt es sich natürlich nicht um alle Objekte, sondern um die sogenannten Business-Objekte. Sie werden oft auch als Behälter bezeichnet. Die Säulen dieses Ansatzes erwachsen aus dem fünften Prinzip von SOLID, das die Abschaffung von Klassen und den Übergang zu Schnittstellen erfordert:

  • Module der obersten Ebene sollten nicht von Modulen der unteren Ebene abhängig sein. Sowohl diese als auch andere sollten auf Abstraktionen basieren.
  • Abstraktionen sollten nicht von Details abhängen. Die Implementierung muss von der Abstraktion abhängen.

Module sollten keine Verweise auf bestimmte Implementierungen enthalten und alle Abhängigkeiten und Interaktionen zwischen ihnen sollten ausschließlich auf der Grundlage von Abstraktionen (d. h. Schnittstellen) aufgebaut sein. Das Wesentliche dieser Regel lässt sich in einem Satz zusammenfassen: Alle Abhängigkeiten müssen in Form von Schnittstellen vorliegen .

Trotz ihrer grundlegenden Natur und scheinbaren Einfachheit wird diese Regel am häufigsten verletzt. Jedes Mal, wenn wir den neuen Operator im Code des Programms/Moduls verwenden und ein neues Objekt eines bestimmten Typs erstellen, entsteht somit eine Abhängigkeit von der Implementierung, anstatt von der Schnittstelle abhängig zu sein.

Es ist klar, dass dies nicht vermieden werden kann und Objekte irgendwo erstellt werden müssen. Zumindest müssen Sie jedoch die Anzahl der Stellen minimieren, an denen dies geschieht und an denen Klassen explizit angegeben werden, und solche Stellen lokalisieren und isolieren, damit sie nicht über den gesamten Programmcode verstreut sind.

Eine sehr gute Lösung ist die verrückte Idee, die Erstellung neuer Objekte auf spezialisierte Objekte und Module zu konzentrieren – Fabriken, Service Locators, IoC-Container.

In gewissem Sinne folgt eine solche Entscheidung dem Single-Choice-Prinzip, das besagt: „Immer wenn ein Softwaresystem viele Alternativen unterstützen muss, sollte ihre vollständige Liste nur einem Modul des Systems bekannt sein . “

Поэтому, если в будущем придется добавить новые варианты (oder новые реализации, Wie в рассматриваемом нами случае создания новых ein Objektов), то достаточно будет произвести обновление только того модуля, в котором содержится эта информация, а все остальные модули останутся незатронутыми и смогут продолжать свою работу wie gewöhnlich.

Beispiel 1

new ArrayList Anstatt etwas wie zu schreiben , wäre es für das JDK sinnvoll List.new(), Ihnen die korrekte Implementierung eines Blatts bereitzustellen: ArrayList, LinkedList oder sogar ConcurrentList.

Beispielsweise erkennt der Compiler, dass das Objekt von verschiedenen Threads aufgerufen wird, und fügt dort eine threadsichere Implementierung ein. Oder zu viele Einfügungen in der Mitte des Blattes, dann basiert die Implementierung auf LinkedList.

Beispiel 2

Dies ist beispielsweise bei Sortierungen bereits geschehen. Wann haben Sie das letzte Mal einen Sortieralgorithmus zum Sortieren einer Sammlung geschrieben? Stattdessen verwendet jetzt jeder die Methode Collections.sort()und die Elemente der Sammlung müssen die Comparable-Schnittstelle (comparable) unterstützen.

Wenn sort()Sie der Methode eine Sammlung mit weniger als 10 Elementen übergeben, ist es durchaus möglich, diese mit einer Blasensortierung (Bubble Sort) und nicht mit Quicksort zu sortieren.

Beispiel 3

Der Compiler beobachtet bereits, wie Sie Zeichenfolgen verketten, und ersetzt Ihren Code durch StringBuilder.append().

9.2 Abhängigkeitsumkehr in der Praxis

Jetzt das Interessanteste: Lasst uns darüber nachdenken, wie wir Theorie und Praxis verbinden können. Wie können Module ihre „Abhängigkeiten“ korrekt erstellen und empfangen, ohne die Abhängigkeitsinversion zu verletzen?

Dazu müssen Sie beim Entwurf eines Moduls selbst entscheiden:

  • was das Modul tut, welche Funktion es ausführt;
  • dann benötigt das Modul von seiner Umgebung, d. h. mit welchen Objekten/Modulen es sich befassen muss;
  • Und wie wird er es bekommen?

Um den Prinzipien der Abhängigkeitsinversion zu entsprechen, müssen Sie unbedingt entscheiden, welche externen Objekte Ihr Modul verwendet und wie es Verweise auf diese erhält.

Und hier sind folgende Optionen:

  • das Modul selbst erstellt Objekte;
  • das Modul entnimmt Objekte aus dem Container;
  • Das Modul hat keine Ahnung, woher die Objekte kommen.

Das Problem besteht darin, dass Sie zum Erstellen eines Objekts einen Konstruktor eines bestimmten Typs aufrufen müssen und das Modul daher nicht von der Schnittstelle, sondern von der spezifischen Implementierung abhängt. Wenn wir jedoch nicht möchten, dass Objekte explizit im Modulcode erstellt werden, können wir das Factory-Methodenmuster verwenden .

„Die Quintessenz ist, dass wir, anstatt ein Objekt direkt über new zu instanziieren, der Client-Klasse eine Schnittstelle zum Erstellen von Objekten zur Verfügung stellen. Da eine solche Schnittstelle immer mit dem richtigen Design überschrieben werden kann, erhalten wir eine gewisse Flexibilität bei der Verwendung von Low-Level-Modulen.“ in High-Level-Modulen“ .

In Fällen, in denen es notwendig ist, Gruppen oder Familien verwandter Objekte zu erstellen, wird eine abstrakte Factory anstelle einer Factory- Methode verwendet .

9.3 Verwendung des Service Locator

Das Modul übernimmt die benötigten Objekte von demjenigen, der sie bereits hat. Es wird davon ausgegangen, dass das System über ein Repository mit Objekten verfügt, in dem Module ihre Objekte „ablegen“ und Objekte aus dem Repository „nehmen“ können.

Dieser Ansatz wird durch das Service Locator-Muster implementiert , dessen Hauptidee darin besteht, dass das Programm über ein Objekt verfügt, das weiß, wie alle möglicherweise erforderlichen Abhängigkeiten (Dienste) abgerufen werden.

Der Hauptunterschied zu Fabriken besteht darin, dass Service Locator keine Objekte erstellt, sondern tatsächlich bereits instanziierte Objekte enthält (oder weiß, wo/wie man sie bekommt, und wenn es erstellt, dann nur einmal beim ersten Aufruf). Die Fabrik erstellt bei jedem Aufruf ein neues Objekt, an dem Sie die volle Kontrolle haben und mit dem Sie machen können, was Sie wollen.

Wichtig ! Der Service-Locator erzeugt Verweise auf dieselben bereits vorhandenen Objekte . Daher müssen Sie mit den vom Service Locator ausgegebenen Objekten sehr vorsichtig sein, da diese gleichzeitig von einer anderen Person verwendet werden können.

Objekte im Service Locator können direkt über die Konfigurationsdatei hinzugefügt werden, und zwar auf jede für den Programmierer bequeme Weise. Der Service Locator selbst kann eine statische Klasse mit einer Reihe statischer Methoden, ein Singleton oder eine Schnittstelle sein und über einen Konstruktor oder eine Methode an die erforderlichen Klassen übergeben werden.

Der Service Locator wird manchmal als Anti-Pattern bezeichnet und wird nicht empfohlen (da er implizite Verbindungen herstellt und nur den Anschein eines guten Designs erweckt). Sie können mehr von Mark Seaman lesen:

9.4 Abhängigkeitsinjektion

Das Modul kümmert sich überhaupt nicht um das „Mining“ von Abhängigkeiten. Es bestimmt nur, was es zum Funktionieren benötigt, und alle notwendigen Abhängigkeiten werden von außen von jemand anderem bereitgestellt (eingeführt).

Dies nennt man „ Abhängigkeitsinjektion “. Typischerweise werden die erforderlichen Abhängigkeiten entweder als Konstruktorparameter (Constructor-Injection) oder über Klassenmethoden (Setter-Injection) übergeben.

Dieser Ansatz kehrt den Prozess der Erstellung von Abhängigkeiten um – statt des Moduls selbst wird die Erstellung von Abhängigkeiten von jemandem von außen gesteuert. Das Modul des aktiven Emitters von Objekten wird passiv – nicht er erschafft, sondern andere erschaffen für ihn.

Dieser Richtungswechsel wird als Inversion of Control oder Hollywood-Prinzip bezeichnet – „Rufen Sie uns nicht an, wir rufen Sie an.“

Dies ist die flexibelste Lösung und verleiht den Modulen die größte Autonomie . Wir können sagen, dass nur es das „Single-Responsibility-Prinzip“ vollständig umsetzt – das Modul sollte sich voll und ganz darauf konzentrieren, seine Arbeit gut zu machen und sich um nichts anderes zu kümmern.

Die Versorgung des Moduls mit allem, was für die Arbeit notwendig ist, ist eine separate Aufgabe, die vom entsprechenden „Spezialisten“ übernommen werden sollte (normalerweise ist ein bestimmter Container, ein IoC-Container, für die Verwaltung von Abhängigkeiten und deren Implementierung verantwortlich).

Tatsächlich ist hier alles wie im Leben: In einem gut organisierten Unternehmen programmieren Programmierer, und die Schreibtische, Computer und alles, was sie zum Arbeiten brauchen, werden vom Büroleiter gekauft und bereitgestellt. Oder, wenn Sie die Metapher des Programms als Konstruktor verwenden, sollte das Modul nicht an Drähte denken, sondern jemand anderes ist am Zusammenbau des Konstruktors beteiligt und nicht an den Teilen selbst.

Es wäre keine Übertreibung zu sagen, dass die Verwendung von Schnittstellen zur Beschreibung von Abhängigkeiten zwischen Modulen (Abhängigkeitsinversion) sowie die korrekte Erstellung und Injektion dieser Abhängigkeiten (hauptsächlich Abhängigkeitsinjektion) Schlüsseltechniken für die Entkopplung sind .

Sie dienen als Grundlage für die lose Kopplung des Codes, seine Flexibilität, Widerstandsfähigkeit gegen Änderungen, Wiederverwendung und ohne die alle anderen Techniken wenig Sinn machen. Dies ist die Grundlage für eine lose Kopplung und eine gute Architektur.

Das Prinzip der Inversion of Control (zusammen mit Dependency Injection und Service Locator) wird ausführlich von Martin Fowler diskutiert. Es gibt Übersetzungen seiner beiden Artikel: „Inversion of Control Containers and the Dependency Injection Pattern“ und „Inversion of Control“ .

Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare