Single Responsibility (SRP), Öffnen / Schließen, Liskov-Vertretung, Schnittstellentrennung und Inversion der Abhängigkeit. Fünf agile Prinzipien, die Sie bei jedem Schreibvorgang anleiten sollten.
Eine Klasse sollte nur einen Grund haben, sich zu ändern.
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, ist es eines der fünf agilen Prinzipien von SOLID. Was es sagt, ist sehr einfach, aber das Erreichen dieser Einfachheit kann sehr schwierig sein. Eine Klasse sollte nur einen Grund haben, sich zu ändern.
Aber warum? Warum ist es so wichtig, nur einen Grund für Veränderungen zu haben??
In statisch typisierten und kompilierten Sprachen können verschiedene Gründe zu mehreren unerwünschten Umsetzungen führen. Wenn es zwei unterschiedliche Gründe für eine Änderung gibt, ist es denkbar, dass zwei verschiedene Teams aus zwei verschiedenen Gründen mit demselben Code arbeiten können. Jeder muss seine Lösung bereitstellen, was bei einer kompilierten Sprache (wie C ++, C # oder Java) zu inkompatiblen Modulen mit anderen Teams oder anderen Teilen der Anwendung führen kann.
Auch wenn Sie möglicherweise keine kompilierte Sprache verwenden, müssen Sie möglicherweise dieselbe Klasse oder das gleiche Modul aus verschiedenen Gründen erneut testen. Dies bedeutet mehr QS-Arbeit, Zeit und Aufwand.
Die Bestimmung der einzelnen Verantwortlichkeiten, die eine Klasse oder ein Modul haben sollte, ist viel komplexer als das Betrachten einer Checkliste. Ein Anhaltspunkt, um herauszufinden, warum wir uns verändert haben, besteht darin, das Publikum für unsere Klasse zu analysieren. Die Benutzer der von uns entwickelten Anwendung oder des Systems, die von einem bestimmten Modul bedient werden, fordern Änderungen an. Die Bedienten bitten um Änderung. Hier sind ein paar Module und ihre möglichen Zielgruppen.
Die Zuordnung konkreter Personen zu allen diesen Rollen kann schwierig sein. In einem kleinen Unternehmen muss eine einzelne Person möglicherweise mehrere Rollen erfüllen, während in einem großen Unternehmen mehrere Personen einer einzelnen Rolle zugewiesen werden können. Es scheint also viel sinnvoller, über die Rollen nachzudenken. Rollen an sich sind jedoch schwer zu definieren. Was ist eine Rolle? Wie finden wir es? Es ist viel einfacher, sich Schauspieler vorzustellen, die diese Rollen spielen und unser Publikum mit diesen Schauspielern in Verbindung bringen.
Wenn also unser Publikum Gründe für Veränderungen definiert, definieren die Akteure das Publikum. Dies hilft uns, das Konzept konkreter Personen wie "John the architect" auf Architecture oder "Mary the referent" auf Operations zu reduzieren.
Eine Verantwortung ist also eine Familie von Funktionen, die einem bestimmten Akteur dient. (Robert C. Martin)
Im Sinne dieser Argumentation werden die Akteure zu einer Quelle der Veränderung für die Funktionsfamilie, die ihnen dient. Wenn sich ihre Bedürfnisse ändern, muss sich auch diese spezifische Funktionsfamilie ändern, um ihren Bedürfnissen gerecht zu werden.
Ein Akteur für eine Verantwortung ist die einzige Quelle der Veränderung für diese Verantwortung. (Robert C. Martin)
Nehmen wir an, wir haben eine Buch
Klasse, die das Konzept eines Buches und seine Funktionalitäten einschließt.
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function printCurrentPage () echo "Inhalt der aktuellen Seite";
Das kann wie eine vernünftige Klasse aussehen. Wir haben Buch, es kann seinen Titel, Autor und es kann die Seite drehen. Schließlich ist es auch möglich, die aktuelle Seite auf dem Bildschirm zu drucken. Es gibt aber ein kleines Problem. Wenn wir über die Akteure nachdenken, die an der Bedienung der beteiligt sind Buch
Objekt, wer könnten sie sein? Wir können uns hier leicht zwei verschiedene Akteure vorstellen: Buchverwaltung (wie der Bibliothekar) und Datenpräsentationsmechanismus (wie wir dem Benutzer den Inhalt zur Verfügung stellen möchten - auf dem Bildschirm, grafische Benutzeroberfläche, Textbenutzeroberfläche, möglicherweise Drucken) . Dies sind zwei sehr unterschiedliche Schauspieler.
Das Mischen von Geschäftslogik mit Präsentation ist schlecht, weil dies gegen das Prinzip der einmaligen Verantwortung (Single Responsibility Principle, SRP) verstößt. Schauen Sie sich den folgenden Code an:
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function getCurrentPage () return "Inhalt der aktuellen Seite"; Schnittstelle Printer function printPage ($ page); class PlainTextPrinter implementiert Printer Funktion printPage ($ page) echo $ page; Klasse HtmlPrinter implementiert Printer Funktion printPage ($ page) echo ''. $ Seite. '';
Selbst dieses sehr einfache Beispiel zeigt, wie die Trennung der Darstellung von der Geschäftslogik und die Beachtung von SRP große Vorteile für die Flexibilität unseres Designs bietet.
Ein ähnliches Beispiel zu dem obigen Beispiel ist, wenn ein Objekt sich selbst speichern und von der Präsentation abrufen kann.
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function getCurrentPage () return "Inhalt der aktuellen Seite"; function save () $ filename = '/ documents /'. $ this-> getTitle (). '-'. $ this-> getAuthor (); file_put_contents ($ filename, serialize ($ this));
Wir können wieder mehrere Akteure wie Book Management System und Persistence identifizieren. Wann immer wir die Persistenz ändern wollen, müssen wir diese Klasse ändern. Wann immer wir ändern möchten, wie wir von einer Seite zur nächsten gelangen, müssen wir diese Klasse ändern. Hier gibt es mehrere Achsen der Veränderung.
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function getCurrentPage () return "Inhalt der aktuellen Seite"; class SimpleFilePersistence function save (Buch $ book) $ filename = '/ documents /'. $ book-> getTitle (). '-'. $ book-> getAuthor (); file_put_contents ($ filename, serialize ($ book));
Wenn Sie die Persistenzoperation in eine andere Klasse verlagern, werden die Verantwortlichkeiten klar voneinander getrennt, und wir können Persistenzmethoden austauschen, ohne unsere zu beeinträchtigen Buch
Klasse. Zum Beispiel die Implementierung eines DatenbankPersistenz
Die Klasse wäre trivial, und unsere Geschäftslogik, die auf Operationen mit Büchern basiert, wird sich nicht ändern.
In meinen vorherigen Artikeln habe ich häufig das übergeordnete Architekturschema erwähnt und vorgestellt, das unten zu sehen ist.
Wenn wir dieses Schema analysieren, können Sie sehen, wie das Prinzip der Einzelverantwortung respektiert wird. Die Objekterstellung ist in Fabriken und dem Haupteinstiegspunkt unserer Anwendung rechts voneinander getrennt, ein Akteur mit einer Verantwortung. Auch für die Persistenz wird unten gesorgt. Ein separates Modul für die separate Verantwortung. Auf der linken Seite haben wir einen Präsentations- oder Lieferungsmechanismus, wenn Sie dies wünschen, in Form einer MVC oder einer anderen Art von Benutzeroberfläche. SRP erneut respektiert. Es bleibt nur noch herauszufinden, was in unserer Geschäftslogik zu tun ist.
Wenn wir über die Software nachdenken, die wir schreiben müssen, können wir viele verschiedene Aspekte analysieren. Beispielsweise können mehrere Anforderungen, die dieselbe Klasse betreffen, eine Änderungsachse darstellen. Diese Achsen der Veränderung können ein Hinweis auf eine einzelne Verantwortung sein. Es besteht eine hohe Wahrscheinlichkeit, dass Gruppen von Anforderungen, die sich auf dieselbe Funktionsgruppe auswirken, Gründe haben, sich zu ändern oder überhaupt festgelegt zu werden.
Der Hauptwert von Software ist die einfache Änderung. Die sekundäre Funktion ist Funktionalität, um möglichst viele Anforderungen zu erfüllen und die Anforderungen des Benutzers zu erfüllen. Um einen hohen Sekundärwert zu erreichen, ist jedoch ein Primärwert zwingend erforderlich. Um unseren primären Wert hoch zu halten, müssen wir ein Design haben, das leicht zu ändern, zu erweitern, neuen Funktionalitäten anzupassen und sicherzustellen, dass SRP respektiert wird.
Wir können Schritt für Schritt überlegen:
Wenn wir also unsere Software entwerfen, sollten wir:
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function getCurrentPage () return "Inhalt der aktuellen Seite"; function getLocation () // gibt die Position in der Bibliothek // zurück. Regalnummer & Raumnummer
Nun mag dies durchaus vernünftig erscheinen. Wir haben keine Methode, um mit Beharrlichkeit oder Präsentation umzugehen. Wir haben unsere turnPage ()
Funktionalität und einige Methoden, um verschiedene Informationen über das Buch bereitzustellen. Wir können jedoch ein Problem haben. Um dies herauszufinden, möchten wir vielleicht unsere Anwendung analysieren. Die Funktion getLocation ()
kann das problem sein.
Alle Methoden der Buch
Klasse geht es um Geschäftslogik. Also muss unsere Perspektive aus der Sicht des Geschäfts sein. Wenn unsere Anwendung für echte Bibliothekare geschrieben wurde, die nach Büchern suchen und uns ein physisches Buch geben, könnte die SRP verletzt werden.
Wir können vermuten, dass die Schauspieleroperationen an den Methoden interessiert sind getTitle ()
, getAuthor ()
und getLocation ()
. Die Kunden haben möglicherweise auch Zugriff auf die Anwendung, um ein Buch auszuwählen und die ersten Seiten zu lesen, um sich ein Bild von dem Buch zu machen und zu entscheiden, ob sie es wollen oder nicht. Die Leser des Schauspielers können sich also für alle Methoden außer interessieren getLocations ()
. Ein gewöhnlicher Kunde kümmert sich nicht darum, wo das Buch in der Bibliothek aufbewahrt wird. Das Buch wird vom Bibliothekar dem Kunden übergeben. Wir haben also tatsächlich einen Verstoß gegen SRP.
class Book function getTitle () return "Ein großartiges Buch"; function getAuthor () return "John Doe"; function turnPage () // Zeiger auf nächste Seite function getCurrentPage () return "Inhalt der aktuellen Seite"; class BookLocator Funktion locate (Book $ book) // gibt die Position in der Bibliothek // zurück. Regalnummer & Raumnummer $ libraryMap-> findBookBy ($ book-> getTitle (), $ book-> getAuthor ());
Vorstellung der BookLocator
, der Bibliothekar wird sich für die interessieren BookLocator
. Der Kunde interessiert sich für die Buch
nur. Natürlich gibt es mehrere Möglichkeiten, um eine zu implementieren BookLocator
. Es kann den Autor und den Titel oder ein Buchobjekt verwenden und erhält die erforderlichen Informationen vom Buch
. Das hängt immer von unserem Geschäft ab. Wichtig ist, dass, wenn die Bibliothek gewechselt wird und der Bibliothekar Bücher in einer anders organisierten Bibliothek finden muss Buch
Objekt wird nicht beeinflusst. Wenn wir uns dazu entschließen, den Lesern eine vorab zusammengestellte Zusammenfassung zur Verfügung zu stellen, anstatt sie durch die Seiten blättern zu lassen, hat dies weder Auswirkungen auf den Bibliothekar noch auf die Suche nach dem Regal, auf dem sich die Bücher befinden.
Wenn es jedoch unser Geschäft ist, den Bibliothekar zu beseitigen und einen Selbstbedienungsmechanismus in unserer Bibliothek zu erstellen, können wir in unserem ersten Beispiel berücksichtigen, dass SRP respektiert wird. Die Leser sind auch unsere Bibliothekare. Sie müssen das Buch selbst finden und es dann im automatisierten System überprüfen. Dies ist auch eine Möglichkeit. Wichtig ist hierbei, dass Sie Ihr Geschäft immer sorgfältig prüfen müssen.
Das Prinzip der Einzelverantwortung sollte immer beachtet werden, wenn wir Code schreiben. Das Klassen- und Moduldesign ist davon stark betroffen und führt zu einem niedrigen gekoppelten Design mit weniger und leichteren Abhängigkeiten. Aber wie jede Münze hat sie zwei Gesichter. Es ist verlockend, von Anfang an unsere Anwendung im Hinblick auf SRP zu entwerfen. Es ist auch verlockend, so viele Akteure zu identifizieren, wie wir wollen oder brauchen. Dies ist jedoch aus Sicht des Designs tatsächlich gefährlich, von Anfang an an alle Parteien zu denken. Übermäßige SRP-Überlegungen können leicht zu einer vorzeitigen Optimierung führen. Anstelle eines besseren Designs kann es zu einer verstreuten Frage kommen, in der die klaren Verantwortlichkeiten von Klassen oder Modulen schwer verständlich sind.
Wenn Sie also bemerken, dass sich eine Klasse oder ein Modul aus verschiedenen Gründen zu ändern beginnt, zögern Sie nicht, ergreifen Sie die notwendigen Schritte, um SRP zu respektieren, aber überfordern Sie es nicht, denn eine vorzeitige Optimierung kann Sie leicht täuschen.