German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)
Die Single Responsibility (SRP), Offen / Closed (OCP), Liskov Substitution, Interface Segregation und Abhängigkeitsinversion. Fünf agile Prinzipien, die Sie bei jedem Code-Code führen sollten.
Es wäre ungerecht, Ihnen zu sagen, dass einer der SOLID-Grundsätze wichtiger ist als ein anderer. Wahrscheinlich hat jedoch keiner der anderen einen so unmittelbaren und tiefgreifenden Effekt auf Ihren Code wie das Dependency Inversion Principle oder kurz DIP. Wenn Sie feststellen, dass die anderen Prinzipien schwer zu verstehen oder anzuwenden sind, beginnen Sie mit diesem und wenden Sie den Rest auf Code an, der DIP bereits berücksichtigt.
Definition
A. High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beide sollten 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 Entwicklung, Prinzipien, Muster und Praktiken definiert und später in der C # -Version des Buches Agile Principles, Patterns und Practices in C # neu veröffentlicht, und es ist das letzte der fünf SOLID agile Prinzipien.
DIP in der realen Welt
Bevor wir anfangen zu programmieren, 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 versuchten, unser Bestes zu geben, waren nicht alle unsere Projekte so nett. Wir gingen durch die Hölle und wieder zurück und wir lernten viele Dinge durch Versuch und Irrtum.
Die SOLID-Prinzipien und die Prinzipien der sauberen Architektur von Onkel Bob (Robert C. Martin) wurden für uns zu einem Spiel-Wechsler und veränderten unsere Art des Codierens auf schwer beschreibbare Weise. Ich werde versuchen, auf den Punkt gebracht, einige wichtige architektonische Entscheidungen von DIP, die einen großen Einfluss auf unsere Projekte hatten.
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 Art in der Datenbank enden müssen. Der Leim zwischen den beiden kann mit PHP versehen werden.
Was davon wegzudenken ist, ist, wie schön die drei Technologien drei verschiedene Architekturen darstellen: Benutzeroberfläche, Geschäftslogik und Persistenz. Wir werden in einer Minute über die Auswirkungen dieser Schichten sprechen. Konzentrieren wir uns zunächst auf einige seltsame, aber häufig anzutreffende Lösungen, um die Technologien zusammenzuarbeiten.
Oft habe ich Projekte gesehen, die SQL-Code in einem PHP-Tag innerhalb einer HTML-Datei verwendeten, oder PHP-Code, der Seiten und Seiten von HTML widerspiegelt und direkt die globalen Variablen $_GET oder $_POST interpretierte. Aber warum ist das schlecht?



Die obigen Bilder stellen eine rohe Version dessen dar, was wir im vorherigen Abschnitt beschrieben haben. Die Pfeile stellen verschiedene Abhängigkeiten dar, und wie wir schließen können, hängt im Grunde alles von allem ab. Wenn wir eine Datenbanktabelle ändern müssen, können wir am Ende eine HTML-Datei bearbeiten. Oder wenn wir ein Feld in HTML ändern, ändern wir möglicherweise den Namen einer Spalte in einer SQL-Anweisung. Oder wenn wir uns das zweite Schema anschauen, müssen wir sehr wahrscheinlich unser PHP modifizieren, wenn sich der HTML-Code ändert, oder in sehr schlimmen Fällen, wenn wir den gesamten HTML-Inhalt aus einer PHP-Datei erzeugen, müssen wir sicher eine PHP-Datei ändern HTML-Inhalt ändern. Es besteht also kein Zweifel, die Abhängigkeiten sind im Zickzack zwischen den Klassen und Modulen. Aber es endet hier 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 machen andere SQL-Abfragen, die unterschiedlichen PHP-Code zurückgeben, und der Zyklus wird fortgesetzt, bis schließlich alle Informationen erhalten und zurückgegeben werden ... wahrscheinlich zur 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 implementierten Projekt gearbeitet haben, werden Sie es sicherlich in Ihrer zukünftigen Karriere tun. Die meisten existierenden Projekte, unabhängig von den verwendeten Programmiersprachen, wurden nach alten Prinzipien geschrieben, von Programmierern, denen das nicht wichtig war oder die nicht genug wussten, um es besser zu machen. Wenn Sie diese Tutorials lesen, sind Sie wahrscheinlich ein höheres Level. Sie sind den 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 Konsequenzen zu leben. In Syneto, nachdem eines unserer Projekte wegen seiner alten und von der Architektur abhängigen Architektur einen fast unhaltbaren Zustand erreicht hatte und wir es für immer aufgeben mussten, beschlossen wir, diesen Weg nie wieder zu verlassen. Seitdem haben wir uns bemüht, eine saubere Architektur zu haben, die die SOLID-Prinzipien und vor allem das Dependency Inversion-Prinzip richtig einhält.



Was ist so erstaunlich an dieser Architektur ist, wie die Abhängigkeiten zeigen:
- Die Benutzeroberfläche (in den meisten Fällen ein Web-MVC-Framework) oder was auch immer für einen anderen Bereitstellungsmechanismus für Ihr Projekt gilt, hängt von der Geschäftslogik ab. Geschäftslogik ist ziemlich abstrakt. Eine Benutzeroberfläche ist sehr konkret. Das UI ist nur ein Detail für das Projekt und es ist 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, wie Sie es wünschen. Wenn Sie morgen MySQL mit PostgreSQL oder einfach nur Textdateien ändern wollen, können Sie das tun. Sie müssen natürlich eine bestimmte Persistenzschicht für die neue Persistenzmethode implementieren, aber Sie müssen keine einzelne Codezeile in Ihrer Geschäftslogik ändern. Es gibt eine ausführlichere Erläuterung zum Thema "Persistenz" im Lernprogramm "Entwicklung in Richtung Persistenzschicht".
- Schließlich haben wir außerhalb der Geschäftslogik alle Klassen, die Geschäftslogikklassen erstellen. Dies sind Fabriken und Klassen, die durch den Eingangspunkt zu unserer Anwendung erstellt werden. Viele Leute denken, dass diese zur Geschäftslogik gehören, aber während sie Geschäftsobjekte erstellen, ist ihr einziger Grund, dies zu tun. Sie sind Klassen, die uns helfen, andere Klassen zu erstellen. Die Geschäftsobjekte und die Logik, die sie bereitstellen, sind unabhängig von diesen Fabriken. Wir könnten verschiedene Muster wie Simple Factory, Abstract Factory, Builder oder einfache Objekterstellung verwenden, um die Geschäftslogik bereitzustellen. Es ist egal. Sobald die Geschäftsobjekte erstellt sind, können sie ihre Arbeit erledigen.
Zeig mir den Code
Das Anwenden des Dependency Inversion Principle (DIP) auf einer Architekturebene ist ziemlich einfach, wenn Sie die klassischen agilen Entwurfsmuster respektieren. Trainieren und exemplifizieren Sie es innerhalb der Geschäftslogik ist auch ziemlich einfach und kann sogar Spaß machen. Wir stellen uns eine E-Book-Reader-Anwendung vor.
1 |
class Test extends PHPUnit_Framework_TestCase { |
2 |
|
3 |
function testItCanReadAPDFBook() { |
4 |
$b = new PDFBook(); |
5 |
$r = new PDFReader($b); |
6 |
|
7 |
$this->assertRegExp('/pdf book/', $r->read()); |
8 |
}
|
9 |
|
10 |
}
|
11 |
|
12 |
class PDFReader { |
13 |
|
14 |
private $book; |
15 |
|
16 |
function __construct(PDFBook $book) { |
17 |
$this->book = $book; |
18 |
}
|
19 |
|
20 |
function read() { |
21 |
return $this->book->read(); |
22 |
}
|
23 |
|
24 |
}
|
25 |
|
26 |
class PDFBook { |
27 |
|
28 |
function read() { |
29 |
return "reading a pdf book."; |
30 |
}
|
31 |
}
|
Wir beginnen mit der Entwicklung unseres E-Readers als PDF-Reader . So weit, ist es gut. Wir haben eine PDFReader-Klasse mit einem PDFBook. Die Funktion read() des Lesers delegiert an die read() -Methode des Buchs. Wir überprüfen dies einfach, indem wir eine Regex-Prüfung nach einem Schlüsselteil der Zeichenkette durchführen, die von der reader() - Methode von PDFBook zurückgegeben wird.
Bitte beachten Sie, dass dies nur ein Beispiel ist. Wir werden die Leselogik von PDF-Dateien oder anderen Dateiformaten nicht implementieren. Deshalb werden unsere Tests einfach nach einigen grundlegenden Strings suchen. Wenn wir die eigentliche Anwendung schreiben würden, wäre der einzige Unterschied, wie wir die verschiedenen Dateiformate testen. Die Abhängigkeitsstruktur wäre unserem Beispiel sehr ähnlich.



Einen PDF-Reader mit einem PDF-Buch zu verwenden, kann eine solide Lösung für eine begrenzte Anwendung sein. Wenn unser Umfang wäre, einen PDF-Reader und nichts mehr zu schreiben, wäre es tatsächlich eine akzeptable Lösung. Aber wir wollen einen generischen E-Book-Reader schreiben, der mehrere Formate unterstützt, darunter unsere erste realisierte PDF-Version. Lass uns unsere Leserklasse umbenennen.
1 |
class Test extends PHPUnit_Framework_TestCase { |
2 |
|
3 |
function testItCanReadAPDFBook() { |
4 |
$b = new PDFBook(); |
5 |
$r = new EBookReader($b); |
6 |
|
7 |
$this->assertRegExp('/pdf book/', $r->read()); |
8 |
}
|
9 |
|
10 |
}
|
11 |
|
12 |
class EBookReader { |
13 |
|
14 |
private $book; |
15 |
|
16 |
function __construct(PDFBook $book) { |
17 |
$this->book = $book; |
18 |
}
|
19 |
|
20 |
function read() { |
21 |
return $this->book->read(); |
22 |
}
|
23 |
|
24 |
}
|
25 |
|
26 |
class PDFBook { |
27 |
|
28 |
function read() { |
29 |
return "reading a pdf book."; |
30 |
}
|
31 |
}
|
Umbenennen hatte keine funktionalen Gegeneffekte. Die Tests sind noch nicht abgeschlossen.
Der Test begann um 13.04 Uhr ...
PHPUnit 3.7.28 von Sebastian Bergmann.
Zeit: 13 ms, Speicher: 2.50Mb
OK (1 Test, 1 Behauptung)
Prozess beendet mit Exit-Code 0
Aber es hat einen ernsten Design-Effekt.



Unser Leser wurde viel abstrakter. Viel allgemeiner. Wir haben einen generischen EBookReader, der einen sehr spezifischen Buchtyp, PDFBook, verwendet. 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 davon abhängig sein.
1 |
class Test extends PHPUnit_Framework_TestCase { |
2 |
|
3 |
function testItCanReadAPDFBook() { |
4 |
$b = new PDFBook(); |
5 |
$r = new EBookReader($b); |
6 |
|
7 |
$this->assertRegExp('/pdf book/', $r->read()); |
8 |
}
|
9 |
|
10 |
}
|
11 |
|
12 |
interface EBook { |
13 |
function read(); |
14 |
}
|
15 |
|
16 |
class EBookReader { |
17 |
|
18 |
private $book; |
19 |
|
20 |
function __construct(EBook $book) { |
21 |
$this->book = $book; |
22 |
}
|
23 |
|
24 |
function read() { |
25 |
return $this->book->read(); |
26 |
}
|
27 |
|
28 |
}
|
29 |
|
30 |
class PDFBook implements EBook{ |
31 |
|
32 |
function read() { |
33 |
return "reading a pdf book."; |
34 |
}
|
35 |
}
|
Die häufigste und am häufigsten verwendete Lösung zum Umkehren der Abhängigkeit besteht darin, ein abstrakteres Modul in unser Design einzuführen. "Das abstrakteste Element in OOP ist ein Interface. Somit kann jede andere Klasse von einem Interface abhängig sein und trotzdem DIP respektieren."
Wir haben eine Schnittstelle für unseren Leser geschaffen. Die Schnittstelle heißt EBook und repräsentiert die Bedürfnisse des EBookReader. Dies ist ein direktes Ergebnis der Respektierung des Interface Separation Principle (ISP), das die Idee fördert, dass Schnittstellen die Bedürfnisse der Kunden widerspiegeln sollten. Schnittstellen gehören zu den Clients, und daher sind sie so benannt, dass sie die Typen und Objekte widerspiegeln, die die Clients benötigen, und sie enthalten Methoden, die die Clients verwenden möchten. Es ist nur natürlich für einen EBookReader, EBooks zu verwenden und eine read() -Methode zu haben.



Statt einer einzelnen Abhängigkeit haben wir jetzt zwei Abhängigkeiten.
- Die erste Abhängigkeit zeigt von
EBookReaderauf dieEBook-Schnittstelle und ist vom Typ usage.EBookReaderverwendetEBooks. - Die zweite Abhängigkeit ist anders. Es zeigt von
PDFBookauf die gleicheEBook-Schnittstelle, ist aber vom Typ Implementierung. EinPDFBookist nur eine bestimmte Form vonEBookund implementiert diese Schnittstelle, um die Bedürfnisse des Kunden zu erfüllen.
Es überrascht nicht, dass wir mit dieser Lösung auch verschiedene Arten von E-Books in unseren Reader einstecken können. Die einzige Bedingung für all diese Bücher ist, die EBook-Schnittstelle zu erfüllen und sie zu implementieren.
1 |
class Test extends PHPUnit_Framework_TestCase { |
2 |
|
3 |
function testItCanReadAPDFBook() { |
4 |
$b = new PDFBook(); |
5 |
$r = new EBookReader($b); |
6 |
|
7 |
$this->assertRegExp('/pdf book/', $r->read()); |
8 |
}
|
9 |
|
10 |
function testItCanReadAMobiBook() { |
11 |
$b = new MobiBook(); |
12 |
$r = new EBookReader($b); |
13 |
|
14 |
$this->assertRegExp('/mobi book/', $r->read()); |
15 |
}
|
16 |
|
17 |
}
|
18 |
|
19 |
interface EBook { |
20 |
function read(); |
21 |
}
|
22 |
|
23 |
class EBookReader { |
24 |
|
25 |
private $book; |
26 |
|
27 |
function __construct(EBook $book) { |
28 |
$this->book = $book; |
29 |
}
|
30 |
|
31 |
function read() { |
32 |
return $this->book->read(); |
33 |
}
|
34 |
|
35 |
}
|
36 |
|
37 |
class PDFBook implements EBook { |
38 |
|
39 |
function read() { |
40 |
return "reading a pdf book."; |
41 |
}
|
42 |
}
|
43 |
|
44 |
class MobiBook implements EBook { |
45 |
|
46 |
function read() { |
47 |
return "reading a mobi book."; |
48 |
}
|
49 |
}
|
Das wiederum führt uns zum offenen / geschlossenen Prinzip, und der Kreis ist geschlossen.
Das Prinzip der Abhängigkeitsinversion ist eine, die uns alle anderen Prinzipien anführt oder hilft. Respekt DIP wird: Respekt DIP wird:
- Fast zwingt Sie, OCP zu respektieren.
- Erlauben Sie, Verantwortlichkeiten zu trennen.
- Verwenden Sie korrekt Subtyping.
- Abschließende Gedanken
Abschließende Gedanken
Das ist es. Wir sind fertig. Alle Tutorials zu den SOLID-Prinzipien sind vollständig. Für mich persönlich war es eine große Veränderung, diese Prinzipien zu entdecken und Projekte mit ihnen umzusetzen. Ich habe die Art und Weise, wie ich über Design und Architektur denke, komplett verändert, und seitdem kann ich sagen, dass alle Projekte, an denen ich arbeite, exponentiell leichter zu verwalten und zu verstehen sind.
Ich betrachte die SOLID-Prinzipien als eines der wichtigsten Konzepte des objektorientierten Designs. Diese Konzepte, die uns dabei helfen müssen, unseren Code besser zu machen und unser Leben als Programmierer viel einfacher zu machen. Gut entworfener Code ist für Programmierer einfacher zu verstehen. Computer sind intelligent, sie können Code unabhängig von seiner Komplexität verstehen. Auf der anderen Seite haben die Menschen eine begrenzte Anzahl von Dingen, die sie in ihrem aktiven, fokussierten Geist behalten können. Genauer gesagt, die Anzahl solcher Dinge ist Die Magische Zahl Sieben, Plus oder Minus Zwei.
Wir sollten uns bemühen, unseren Code um diese Zahlen herum zu strukturieren, und es gibt verschiedene Techniken, die uns dabei helfen. Funktionen mit maximal vier Zeilen Länge (fünf mit der Definitionszeile eingeschlossen), damit sie alle gleichzeitig in unserem Kopf passen. Einrückungen überschreiten nicht 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 vor fünf Konzepte. Es gibt fünf SOLID-Prinzipien, die jeweils fünf bis neun Unterkonzepte / Module / Klassen erfordern. 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 können, ist die magische Zahl sieben, plus oder minus zwei um uns herum, also warum sollte dein Code anders sein?



