Hallo Spaß-Coder.
Wie gerne würden wir Coder auf der grünen Wiese entwickeln – und damit meine ich nicht in der grellen Sonne. Aber immer wieder geht es darum, bereits vorhandenen Code zu verändern. Ständig ändern sich die Anforderungen: hier noch etwas mehr Features, dort noch etwas weniger Bugs usw.
Im Artikel Aus Prinzip nur eine Verantwortlichkeit haben wir uns das erste der SOLID-Prinzipien angeschaut. Heute geht es um das zweite Design Prinzip.
Open-Closed-Principle (OCP)
Wie auch bei Wikipedia unter https://de.wikipedia.org/wiki/Open-Closed-Prinzip zu lesen ist, besagt das OCP: Klassen, Methoden, etc. sollen sowohl offen (für Erweiterungen), als auch geschlossen (für Änderungen) sein.
Erläuterung des Prinzips
Offen und geschlossen – wie soll das denn aussehen? Mal angenommen, ich packe meinen gesamten Code in eine einzige Klasse mit vielen großen Methoden (so etwas soll es tatsächlich geben). Will oder muss ich nun einer Änderung an der Klasse vornehmen, weil ich zum Beispiel ein Feature hinzufüge oder einen Bug behebe, wer sagt mir dann, dass ich keine ungewollte Verhaltensändeurng der Klasse herbeiführe? Habe ich wirklich die gesamte Klasse im Blick und durchschaue die Abhängigkeiten zwischen den Methoden und Variablen?
Wie oben bereits erwähnt kommen wir um Änderungen an bestehendem Code nicht herum. Aber es ist doch wünschenswert, ein neues Verhalten ganz leicht ergänzen zu können. Oder Veränderung von bestehendem Verhalten an einer sehr kleinen Klasse mit wenigen Methoden vornehmen zu können. Aber wie muss der Code gestaltet werden, damit ich dies erreichen kann?
Zunächst möchte ich klarstellen, dass das OCP insbesondere dann seine Vorteile entfaltet, wenn der betroffene Code häufig geändert wird. Woher weiß ich das jetzt im Voraus? Gar nicht. Daher schreibe ich neuen Code so einfach wie möglich, so dass er exakt die Anforderung erfüllt – nicht mehr und nicht weniger. Auch die erste Änderung am Code nehme ich mit derselben Idee vor. Sobald ich jedoch die zweite Änderung vornehme, vielleicht sogar nach kurzer Zeit, kann ich erahnen, dass dieser Code häufigen Änderungen unterliegen wird. Genau dann ändere ich den Code so, dass zukünftige Erweiterungen sehr leicht möglich sind.
Dazu eignen sich zum Beispiel diese beiden Entwurfsmuster.
- Template Method Pattern
- Strategy Pattern
Beide Entwurfsmuster sind auch in [b.] beschrieben und werden hier nur kurz skizziert.
Das Verhaltensmuster Schablonenmethode (Template Method) stellt im Rahmen der Klassenvererbung Einstiegspunkte bereit, um Verhalten in einer abgeleiteten Klasse zu ergänzen. So wird in einer (abstrakten) Klassen eine Methode implementiert, welche innerhalb der Verarbeitung eine oder mehrere abstrakte Methoden aufruft. Diese Methoden können dann in der Kindklasse mit Leben gefüllt und somit die Verarbeitung mit neuem Verhalten angereichert werden, ohne die Vaterklasse ändern zu müssen.
Das Verhaltensmuster Strategie (Strategy) ermöglicht es, einer Methode ihr Verhalten mitzugeben. Anstelle der x-ten If-Abfrage oder dem weiteren switch-case wird einfach eine Methode einer anderen Klasse aufgerufen – wobei hier gegen ein Interface programmiert wird. Die Implementierung wird dann jeweils abhängig vom Kontext mitgegeben oder bereitgestellt. Dadurch kann ein neues Verhalten als weitere Implementierung des Interfaces erfolgen, ohne die ursprüngliche Klasse ändern zu müssen.
Dies sind nur zwei Möglichkeiten, eine Klasse oder Methode offen für neues Verhalten zu gestalten. Geschlossen gegenüber Veränderungen bleiben diese Klassen und Methoden ebenfalls, da die Verhaltensänderung an anderer Stelle vorgenommen werden kann – nämlich in einer separaten Klasse.
Unsere Erfahrung mit dem OCP
Das OCP haben wir zum Beispiel in dem nie veröffentlichten Kultspiel Magic Conflict (ein rundenbasiertes Strategiespiel) eingesetzt. Der Spieler kann hier verschiedene Aktionen durchführen. Diese Aktionen hätten sicherlich alle mit mehreren If-Blöcken oder switch/case-Anweisungen implementiert werden können. Da uns bei der Entwicklung recht schnell klar war, dass wir die Aktionen zeitnah erweitern wollen, haben wir das Template Method Pattern angewendet. Damit gibt es eine abstrakte Basisklasse PlayerAction mit unter anderem dieser Methode:
1 2 3 4 5 6 7 8 |
public void Execute() { CheckValidity(); ExecuteImplementation(); this.Executed = true; } protected abstract void ExecuteImplementation(); |
In der Methode CheckValidity() wird ein injizierter Validator befragt, ob diese Aktion jetzt gültig ist (zum Beispiel alle Ressourcen verfügbar oder das angegriffene Ziel zulässig ist). Dann wird die eigentliche Aktion durchgeführt, indem die abstrakte Methode ExecuteImplementation() aufgerufen wird, welche in den konkreten Aktionen (je Aktion eine Klasse) implementiert wird.
Auch das Strategy Pattern haben wir an verschiedenen Stellen in unserem Code angewendet und sind der Meinung, dass das OCP es sehr einfach möglich macht, den vorhandenen Code um neue Funktionalität zu ergänzen, ohne die bestehenden Klasse öffnen (also ändern) zu müssen.
Wenn abzusehen ist, dass der vorliegende Code häufig erweitert werden wird, sprechen wir eine klar Empfehlung für das OCP aus.
Zusammenfassung
In diesem Artikel haben wir einen Blick auf das zweite der SOLID-Prinzipien, nämlich das Open-Closed-Principle, geworfen. Methoden, Klassen und Module so zu gestalten, dass neues Verhalten leicht hinzugefügt werden kann, ohne vorhandene Klasse ändern zu müssen, reduziert das Potenzial, bestehenden Code kaputt zu machen. Und so können wir doch ein Stück auf der grünen Wiese programmieren 😉
Viel Spaß beim Anwenden.
Eure Spaß-Coder.
Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:
- Martin, Robert C. Clean Code-Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe. MITP-Verlags GmbH & Co. KG, 2013.
-
Gamma, Erich, et al. Design patterns: elements of reusable object-oriented software. Pearson Education, 1994.