SOLID Teil 4 - Das Prinzip der Abhängigkeitsinversion

Einzelverantwortung (Single Responsibility - SRP), Open / Closed (OCP), Liskov-Substitution, Schnittstellentrennung und Abhängigkeit Inversion. Fünf agile Prinzipien, die Sie bei jedem Schreibvorgang anleiten sollten.

Es wäre ungerecht, Ihnen zu sagen, dass eines der SOLID-Prinzipien wichtiger ist als das andere. Wahrscheinlich hat jedoch keiner der anderen einen so unmittelbaren und tiefgreifenden Einfluss auf Ihren Code wie das Prinzip der Inversion der Abhängigkeit oder kurz DIP. Wenn Sie die anderen Prinzipien nur schwer fassen oder anwenden können, beginnen Sie mit diesem und wenden Sie den Rest auf Code an, der bereits DIP berücksichtigt.

Definition

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

Dieses Prinzip wurde von Robert C. Martin in seinem Buch Agile Software Development, Principles, Patterns und Practices definiert und später in der C # -Version des Buches Agile Principles, Patterns und Practices in C # neu veröffentlicht. Es ist das letzte der fünf SOLIDE agile Prinzipien.

DIP in der realen Welt

Bevor wir mit dem Programmieren beginnen, möchte ich Ihnen eine Geschichte erzählen. Bei Syneto waren wir mit unserem Code nicht immer so vorsichtig. Vor ein paar Jahren wussten wir weniger und obwohl wir uns bemüht haben, unser Bestes zu geben, waren nicht alle unsere Projekte so schön. Wir gingen durch die Hölle und wieder zurück und haben durch Versuch und Irrtum vieles gelernt.

Die SOLID-Prinzipien und die sauberen Architekturprinzipien von Onkel Bob (Robert C. Martin) wurden für uns zu einem Spielveränderer und veränderten unsere Codierweise auf schwer beschreibbare Weise. Ich werde versuchen, kurz die wichtigsten vom DIP auferlegten architektonischen Entscheidungen zu erläutern, die unsere Projekte stark beeinflusst haben.

Die meisten Webprojekte enthalten drei Haupttechnologien: HTML, PHP und SQL. Die jeweilige Version dieser Anwendungen, über die wir sprechen, oder welche Art von SQL-Implementierungen Sie verwenden, ist irrelevant. Die Sache ist, dass Informationen aus einem HTML-Formular auf die eine oder andere Weise in der Datenbank landen müssen. Der Kleber zwischen den beiden kann mit PHP bereitgestellt werden.

Wesentlich ist, dass die drei Technologien drei unterschiedliche Architekturebenen darstellen: Benutzeroberfläche, Geschäftslogik und Persistenz. Wir werden in einer Minute über die Auswirkungen dieser Schichten sprechen. Im Moment konzentrieren wir uns auf einige merkwürdige, aber häufig anzutreffende Lösungen, damit die Technologien zusammenarbeiten.

Ich habe oft Projekte gesehen, die SQL-Code in einem PHP-Tag in einer HTML-Datei verwendet haben, oder PHP-Code, der HTML-Seiten und HTML-Seiten aufgreift und das direkt interpretiert $ _GET oder $ _POST globale Variablen. Aber warum ist das so schlimm??


Die Abbildungen oben zeigen eine Rohfassung dessen, was wir im vorherigen Absatz beschrieben haben. Die Pfeile stellen verschiedene Abhängigkeiten dar, und wie wir daraus schließen können, hängt alles von allem ab. Wenn wir eine Datenbanktabelle ändern müssen, kann es vorkommen, dass Sie eine HTML-Datei bearbeiten. Wenn wir ein Feld in HTML ändern, ändern wir möglicherweise den Namen einer Spalte in einer SQL-Anweisung. Wenn wir uns das zweite Schema ansehen, müssen wir möglicherweise unser PHP ändern, wenn sich der HTML-Code ändert, oder in sehr schlechten Fällen, wenn wir alle HTML-Inhalte aus einer PHP-Datei generieren, in die wir eine PHP-Datei ändern müssen HTML-Inhalt ändern Es besteht kein Zweifel, dass die Abhängigkeiten zwischen den Klassen und Modulen im Zickzack verlaufen. Aber hier endet es nicht. Sie können Prozeduren speichern. PHP-Code in SQL-Tabellen.


Im obigen Schema geben Abfragen an die SQL-Datenbank PHP-Code zurück, der mit Daten aus den Tabellen generiert wurde. Diese PHP-Funktionen oder -Klassen führen andere SQL-Abfragen aus, die anderen PHP-Code zurückgeben. Der Zyklus wird fortgesetzt, bis schließlich alle Informationen abgerufen und zurückgegeben werden… wahrscheinlich an die Benutzeroberfläche.

Ich weiß, dass dies für viele von Ihnen unverschämt klingen mag, aber wenn Sie noch nicht mit einem auf diese Weise erfundenen und umgesetzten Projekt gearbeitet haben, werden Sie dies sicherlich in Ihrer zukünftigen Karriere tun. Die meisten bestehenden Projekte, unabhängig von den verwendeten Programmiersprachen, wurden mit alten Prinzipien von Programmierern geschrieben, die sich nicht darum gekümmert haben oder genug wissen, um es besser zu machen. Wenn Sie diese Tutorials lesen, liegen Sie höchstwahrscheinlich um ein höheres Niveau. Sie sind bereit oder bereit, Ihren Beruf zu respektieren, Ihr Handwerk anzunehmen und es besser zu machen.

Die andere Möglichkeit besteht darin, die Fehler Ihrer Vorgänger zu wiederholen und mit den Folgen zu leben. In Syneto, nachdem eines unserer Projekte aufgrund seiner alten und kreuzabhängigen Architektur einen fast unberührbaren Zustand erreicht hatte und wir es im Grunde für immer aufgeben mussten, entschieden wir uns, diese Straße nie wieder zu verlassen. Seitdem bemühen wir uns um eine saubere Architektur, die die SOLID-Prinzipien und vor allem das Prinzip der Inversion der Abhängigkeit korrekt einhält.


Was an dieser Architektur so erstaunlich ist, ist, wie die Abhängigkeiten zeigen:

  • Die Benutzeroberfläche (in den meisten Fällen ein Web-MVC-Framework) oder der jeweils andere Bereitstellungsmechanismus für Ihr Projekt hängt von der Geschäftslogik ab. Geschäftslogik ist ziemlich abstrakt. Eine Benutzeroberfläche ist sehr konkret. Die Benutzeroberfläche ist nur ein Detail für das Projekt und auch sehr unbeständig. Nichts sollte von der Benutzeroberfläche abhängen, nichts sollte von Ihrem MVC-Framework abhängen.
  • Die andere interessante Beobachtung, die wir machen können, ist, dass die Persistenz, die Datenbank, Ihr MySQL oder PostgreSQL von der Geschäftslogik abhängt. Ihre Geschäftslogik ist datenbankunabhängig. Dies ermöglicht den Austausch von Persistenz nach Ihren Wünschen. Wenn Sie morgen MySQL mit PostgreSQL oder nur Textdateien ändern möchten, können Sie dies tun. Sie müssen natürlich eine bestimmte Persistenzschicht für die neue Persistenzmethode implementieren. Sie müssen jedoch nicht eine einzelne Codezeile in Ihrer Geschäftslogik ändern. Eine ausführlichere Erklärung zum Thema "Persistenz" finden Sie im Tutorial "Evolution in Richtung einer Persistenzschicht".
  • Schließlich haben wir auf der rechten Seite der Geschäftslogik alle Klassen, die Geschäftslogikklassen erstellen. Dies sind Fabriken und Klassen, die vom Einstiegspunkt unserer Anwendung erstellt werden. Viele Menschen neigen dazu, zu glauben, dass diese zur Geschäftslogik gehören, aber während sie Geschäftsobjekte erstellen, ist dies der einzige Grund. Sie sind Klassen, die uns helfen, andere Klassen zu erstellen. Die Geschäftsobjekte und die von ihnen bereitgestellte Logik sind von diesen Fabriken unabhängig. Wir könnten verschiedene Muster verwenden, wie Simple Factory, Abstract Factory, Builder oder einfache Objekterstellung, um die Geschäftslogik bereitzustellen. Es spielt keine Rolle Sobald die Geschäftsobjekte erstellt wurden, können sie ihre Arbeit erledigen.

Zeigen Sie mir den Code

Die Anwendung des Abhängigkeitsinversion-Prinzips (DIP) auf architektonischer Ebene ist recht einfach, wenn Sie die klassischen agilen Designmuster berücksichtigen. Das Trainieren und Veranschaulichen innerhalb der Geschäftslogik ist auch ziemlich einfach und kann sogar Spaß machen. Wir werden uns eine E-Book-Reader-Anwendung vorstellen.

Klasse Test erweitert PHPUnit_Framework_TestCase Funktion testItCanReadAPDFBook () $ b = new PDFBook (); $ r = neuer PDFReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  class PDFReader privates $ book; Funktion __construct (PDFBook $ book) $ this-> book = $ book;  Funktion read () return $ this-> book-> read ();  class PDFBook function read () return "Ein PDF-Buch lesen."; 

Wir beginnen mit der Entwicklung unseres E-Readers als PDF-Reader. So weit, ist es gut. Wir haben ein PDF Reader Klasse mit einem PDFBook. Das lesen() Funktion auf die Leser delegiert an das Buch lesen() Methode. Wir überprüfen dies nur, indem wir eine Regex-Überprüfung durchführen, nachdem ein wichtiger Teil der Zeichenfolge von zurückgegeben wurde PDFBook's Leser() Methode.

Bitte beachten Sie, dass dies nur ein Beispiel ist. Die Leselogik von PDF-Dateien oder anderen Dateiformaten wird nicht implementiert. Deshalb werden unsere Tests einfach nach einigen Grundzeichenfolgen suchen. Wenn wir die eigentliche Anwendung schreiben, besteht der einzige Unterschied darin, wie wir die verschiedenen Dateiformate testen. Die Abhängigkeitsstruktur wäre unserem Beispiel sehr ähnlich.


Wenn Sie einen PDF-Reader mit einem PDF-Buch verwenden, kann dies eine solide Lösung für eine eingeschränkte Anwendung sein. Wenn wir nur einen PDF-Reader schreiben würden, wäre dies eine akzeptable Lösung. Wir möchten jedoch einen generischen E-Book-Reader schreiben, der mehrere Formate unterstützt, darunter unsere erste implementierte Version PDF. Umbenennen wir unsere Leserklasse.

Klasse Test erweitert PHPUnit_Framework_TestCase Funktion testItCanReadAPDFBook () $ b = new PDFBook (); $ r = neuer EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  class EBookReader privates $ book; Funktion __construct (PDFBook $ book) $ this-> book = $ book;  Funktion read () return $ this-> book-> read ();  class PDFBook function read () return "Ein PDF-Buch lesen."; 

Die Umbenennung hatte keine funktionellen Gegeneffekte. Die Tests sind immer noch bestanden.

Der Test begann um 13:04 Uhr…
PHPUnit 3.7.28 von Sebastian Bergmann.
Zeit: 13 ms, Speicher: 2,50 MB
OK (1 Test, 1 Behauptung)
Vorgang mit Exitcode 0 beendet

Aber es hat einen ernsthaften Designeffekt.


Unser Leser wurde viel abstrakter. Viel allgemeiner. Wir haben einen generischen Ebook Reader das verwendet einen ganz bestimmten Buchtyp, PDFBook. Eine Abstraktion hängt von einem Detail ab. Die Tatsache, dass unser Buch vom Typ PDF ist, sollte nur ein Detail sein und niemand sollte sich darauf verlassen.

Klasse Test erweitert PHPUnit_Framework_TestCase Funktion testItCanReadAPDFBook () $ b = new PDFBook (); $ r = neuer EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  Schnittstelle EBook Funktion read ();  class EBookReader privates $ book; Funktion __construct (EBook $ book) $ this-> book = $ book;  Funktion read () return $ this-> book-> read ();  class PDFBook implementiert EBook Funktion read () return "Ein PDF-Buch lesen."; 

Die am häufigsten verwendete und am häufigsten verwendete Lösung zur Umkehrung der Abhängigkeit ist die Einführung eines abstrakteren Moduls in unser Design. "Das abstrakteste Element in OOP ist eine Schnittstelle. Daher kann jede andere Klasse von einer Schnittstelle abhängen und trotzdem DIP berücksichtigen.".

Wir haben eine Schnittstelle erstellt für unseren Leser. Die Schnittstelle wird aufgerufen EBook und vertritt die Bedürfnisse der Ebook Reader. Dies ist ein direktes Ergebnis der Einhaltung des Interface-Segregations-Prinzips (ISP), das die Idee fördert, dass Schnittstellen die Bedürfnisse der Kunden berücksichtigen sollten. Schnittstellen gehören zu den Clients und werden daher benannt, um die Typen und Objekte anzugeben, die der Client benötigt, und sie enthalten Methoden, die der Client verwenden möchte. Es ist nur natürlich für eine Ebook Reader benutzen EBooks und habe eine lesen() Methode.


Anstelle einer einzigen Abhängigkeit haben wir jetzt zwei Abhängigkeiten.

  • Die ersten Abhängigkeitspunkte von Ebook Reader in Richtung der EBook Schnittstelle und es ist vom Typ Verwendung. Ebook Reader Verwendet EBooks.
  • Die zweite Abhängigkeit ist unterschiedlich. Es zeigt von PDFBook auf das gleiche EBook Schnittstelle, aber es ist vom Typ Implementierung. EIN PDFBook ist nur eine bestimmte Form von EBook, und implementiert somit diese Schnittstelle, um die Bedürfnisse des Kunden zu erfüllen.

Es ist nicht überraschend, dass diese Lösung es uns auch ermöglicht, verschiedene Arten von E-Books in unseren Leser einzufügen. Die einzige Bedingung für alle diese Bücher ist die Befriedigung der EBook Schnittstelle und implementieren es.

Klasse Test erweitert PHPUnit_Framework_TestCase Funktion testItCanReadAPDFBook () $ b = new PDFBook (); $ r = neuer EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  Funktion testItCanReadAMobiBook () $ b = new MobiBook (); $ r = neuer EBookReader ($ b); $ this-> assertRegExp ('/ mobi book /', $ r-> read ());  Schnittstelle EBook Funktion read ();  class EBookReader privates $ book; Funktion __construct (EBook $ book) $ this-> book = $ book;  Funktion read () return $ this-> book-> read ();  class PDFBook implementiert EBook Funktion read () return "Ein PDF-Buch lesen.";  Klasse MobiBook implementiert EBook Funktion read () return "ein Mobi-Buch lesen."; 

Was uns wiederum zu The Open / Closed Principle führt, und der Kreis ist geschlossen.

Das Prinzip der Inversion der Abhängigkeit führt oder hilft uns, alle anderen Prinzipien zu respektieren. Die Beachtung von DIP wird:

  • Sie fast zwingen, OCP zu respektieren.
  • Erlauben Sie Ihnen, die Verantwortlichkeiten zu trennen.
  • Stellen Sie sicher, dass Sie Subtyping richtig verwenden.
  • Bieten Sie die Möglichkeit, Ihre Schnittstellen voneinander zu trennen.

Abschließende Gedanken

Das ist es. Wir sind fertig. Alle Tutorials zu den SOLID-Prinzipien sind abgeschlossen. Für mich persönlich war es eine große Veränderung, diese Prinzipien zu entdecken und Projekte zu realisieren. Ich habe meine Denkweise über Design und Architektur grundlegend geändert und kann sagen, seitdem sind alle Projekte, an denen ich arbeite, exponentiell einfacher zu verwalten und zu verstehen.

Ich betrachte die SOLID-Prinzipien als eines der wesentlichsten Konzepte objektorientierten Designs. Diese Konzepte, die uns anleiten müssen, um unseren Code zu verbessern und unser Leben als Programmierer erheblich zu vereinfachen. Gut entworfener Code ist für Programmierer einfacher zu verstehen. Computer sind intelligent, sie können Code unabhängig von seiner Komplexität verstehen. Menschen hingegen haben eine begrenzte Anzahl von Dingen, die sie in ihrem aktiven, fokussierten Geist behalten können. Genauer gesagt ist die Anzahl solcher Dinge The Magical Number Seven, Plus oder Minus Two.

Wir sollten uns bemühen, unseren Code um diese Zahlen herum zu strukturieren, und es gibt verschiedene Techniken, die uns dabei helfen. Funktionen mit einer Länge von maximal vier Zeilen (fünf einschließlich der Definitionszeile), damit sie alle gleichzeitig in unseren Kopf passen. Einrückungen gehen nicht über fünf Ebenen. Klassen mit nicht mehr als neun Methoden. Entwurfsmuster, die normalerweise eine Anzahl von fünf bis neun Klassen verwenden. Unser High-Level-Design in den obigen Schemata verwendet vier bis fünf Konzepte. Es gibt fünf SOLID-Prinzipien, von denen jedes fünf bis neun Unterkonzepte / Module / Klassen als Beispiele benötigt. Die ideale Größe eines Programmierteams liegt zwischen fünf und neun. Die ideale Anzahl von Teams in einem Unternehmen liegt zwischen fünf und neun.

Wie Sie sehen, ist die magische Zahl sieben, plus oder minus zwei überall um uns herum. Warum sollte Ihr Code also anders sein??