Das SOLIDE Die Prinzipien des objektorientierten Designs bieten gute Richtlinien zum Schreiben von Code, der leicht zu ändern ist. Bei einigen Prinzipien können die Motivation und der Wert jedoch schwer zu verstehen sein. Offen geschlossen ist besonders ärgerlich: Software-Entitäten (Klassen, Module, Funktionen usw.) sollten für Erweiterungen offen, für Änderungen jedoch geschlossen sein Was bedeutet es, erweitert, aber nicht verändert zu werden? Führt dieses Prinzip zwangsläufig zu tiefen, verworrenen Klassenhierarchien? Es gab einige Diskussion und Kritik Wir haben kürzlich von SOLID und dem Open/Closed-Prinzip erfahren. Daher möchten wir einige der Erfahrungen des Rescale-Entwicklungsteams beim Verständnis von Open/Closed und der Verwendung dieses Prinzips zum Schreiben von Klassen mit leicht veränderbarem Verhalten teilen. Wir betrachten Open/Closed als Ergänzung zum Single Responsibility Principle, da es Entwickler dazu ermutigt, Klassen mit fokussierten Verantwortlichkeiten zu schreiben, deren Verhalten mit minimalen Codeänderungen leicht veränderbar ist.
Eine Erklärung
Lassen Sie uns zunächst einige unserer ursprünglichen Fragen untersuchen. Was bedeutet es, wenn Code erweitert wird? Was bedeutet es, wenn er geändert wird? Code ist geändert wenn wir den Code selbst ändern – indem wir Bedingungen zu Methoden hinzufügen, zusätzliche Argumente einbeziehen, das Verhalten basierend auf den Eigenschaften von Argumenten ändern usw. Dies ist oft der erste Gedanke der Entwickler, wie sie bestehenden Code dazu bringen können, eine neue Aufgabe zu erfüllen – einfach eine weitere Bedingung hinzufügen. Code ist verlängert wenn wir sein Verhalten ändern, ohne den Code selbst zu modifizieren – indem Einbeziehung verschiedener MitarbeiterDamit unser Code überhaupt für eine solche Erweiterung geeignet ist, müssen wir viele der anderen SOLID-Prinzipien befolgen – d. h. für jeden Aspekt der Aufgabe, die wir erledigen möchten, unterschiedliche Objekte einfügen. Das wiederum erfordert, dass jeder Aspekt der Aufgabe von einem anderen Objekt erledigt wird, was den Kern des Single Responsibility Principle darstellt – Objekte sollten nur eine Aufgabe erfüllen. Open/Closed ermutigt Entwickler, Code zu schreiben, der Verantwortlichkeiten verteilt und den Vorteil bietet, dass sich Verhalten leicht ändern lässt, ohne dass lange Methoden und große Klassen entstehen.
Ein Beispiel
Lassen Sie uns diese Theorie anhand eines Beispiels veranschaulichen. Bei Neu skalieren, wir vor kurzem eine Menge Code umgestaltet einen REST-API-Client zu verwenden. API-Clients werden oft mit unterschiedlichen Methoden für die jeweilige Anfrageart geschrieben, und so begann auch unser Client. Wir hatten eine Schnittstelle wie:
öffentliche Schnittstelle APIClient { Analysis getAnalysis(lange Job-ID); HardwareSummary getHardwareSummary(lange Job-ID); ... }
Jede der Methodenimplementierungen sah folgendermaßen aus:
öffentliche Analyse getAnalysis(lange Job-ID) { String-Pfad = „/api/jobs/“ + Job-ID + „/analysis/“; String-Antwort = makeHttpRequest(neue URL(this.baseApiUrl, Pfad)); return parseJson(Antwort, Analysis.class); }
Das war mühsam. Jedes Mal, wenn wir neue Daten von der API abrufen wollten, mussten wir eine neue, redundante Methode hinzufügen. Dieser Client war nicht offen für eine Erweiterung, und um ihr Verhalten zu ändern, mussten wir die Klasse modifizieren. Das lag an einer Unübersichtlichkeit der Verantwortlichkeiten. Auch wenn die obige Methode einfach aussieht, hat sie tatsächlich mehrere verschiedene Verantwortlichkeiten und entsprechende Kenntnisse. Sie weiß, wie Pfade für jeden Anfragetyp formatiert werden. Sie weiß, wie HTTP-Anfragen an die API gestellt werden und wie JSON für jede Anfrage analysiert wird. Wir haben beschlossen, dies umzugestalten, um unserem API-Client nur eine Verantwortung zu geben – das Stellen von Anfragen. Stattdessen haben wir es erweitert, indem wir Request-Objekte übergeben, die die anderen Verantwortlichkeiten übernehmen. Jetzt haben wir eine API-Client-Schnittstelle mit nur einer Methode:
öffentliche Schnittstelle APIClient { public T get(Request request); }
Da jeder Anforderungstyp relativ statisch ist, verfügen wir über statische Factory-Methoden zum Erstellen von Anforderungsobjekten.
öffentliche Klasse Request { öffentliche statische void Request forAnalysis(Job job) { ... } }
Und dank Java-Generika erhalten Aufrufer den richtigen Objekttyp zurück. Wir haben jetzt natürlich aussehenden Code wie:
Analyseanalyse = Client.get(Request.forAnalysis(this.job));
Und das Abrufen neuer Informationen aus der API ist einfach. Wir müssen den API-Client nicht ändern, sondern erweitern ihn einfach durch die Übergabe einer anderen Anfrage.
Zusammenfassung
Es herrscht große Verwirrung darüber, was Open/Closed bedeutet. Es erfordert jedoch lediglich, dass Objekte fokussierte Verantwortlichkeiten haben und mit anderen eingefügten Objekten interagieren. Auf diese Weise können Entwickler verschiedene Mitarbeiter einfügen, um das Verhalten zu ändern. Jeder, der schon einmal mit einer großen Codebasis gearbeitet hat, kennt Abschnitte, die ohne Open/Closed entwickelt wurden. Dieser Code ist scheinbar einfach und „erledigt einfach die Arbeit“. Das Problem ist, dass sich die Arbeit nur ändern lässt, wenn man den Code ändert, da sich die Geschäftsanforderungen unweigerlich ändern. Methoden sammeln Bedingungen und Switch-Anweisungen an, Klassen sammeln Felder an, und der Code wird auf den ersten Blick schwerer verständlich. Schlechtes Design nährt sich selbst, da der einfachste Weg, eine Aufgabe zu erledigen, darin besteht, einer Methode einfach ein paar Zeilen hinzuzufügen. Wenn Entwickler Open/Closed im Hinterkopf behalten, können sie Möglichkeiten erkennen, Objekte mit erweiterbarem Verhalten zu erstellen und verhindern, dass die Entwicklungskosten außer Kontrolle geraten.
