Dies ist der zweite Blogpost zu den fünf Programmierprinzipien hinter SOLID. Heute geht es um O: Open-Closed Principle.
Ähnlich wie beim im letzten Blogpost behandelten Single Responsibility Principle gibt es auch beim Open-Closed Principle (OCP) kein unumstössliches "richtig" und "falsch" bei der Umsetzung des Prinzipes. Es braucht einige Programmiererfahrung, um dieses Prinzip strategisch sinnvoll einzusetzen. Es ist also auch bei diesem Post wichtig, sich eigene Gedanken zur Thematik zu machen, und allenfalls andere Meinungen und Interpretationen des Prinzipes zu studieren.
Die Absichten des Open-Closed Principles sind hauptsächlich die folgenden:
Während sich die Best Practices für dieses Prinzip im Verlauf der Jahre stark änderte, ist der Leitsatz und auch die Definition seit jeher:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
Meyer, Bertrand (1988). Object-Oriented Software Construction.
"Open for extension" meint, dass Module erweitert werden können, auch wenn die bestehenden Klassen nicht verändert werden dürfen. Dazu muss eine entsprechende Softwarestruktur gegeben sein. Erweiterungen können klassischerweise umgesetzt werden, indem eine neue Klasse erstellt wird, welche von der zu erweiternden Klasse erbt. Ein zeitgemässerer Ansatz ist jedoch Abhängigkeiten zu abstrahieren und Erweiterung mit dem Austauschen von Implementation umzusetzen.
"Closed for modification" meint, dass ein Stück Software, zum Beispiel eine Methode, aber typischerweise meint man ganze Klassen, nicht mehr verändert werden dürfen, wenn diese einmal Teil einer Version der Software ist, welche publiziert und damit abgesegnet wurde. Software wird naturgemäss stets verändert. Nach OCP sollen die Änderungen aber keine bestehenden Klassen betreffen.
Insbesondere der "closed" Teil des Prinzips wird häufig absolut definiert, also dass man absolut gar keinen bestehenden Quellcode verändern darf. Dies ist jedoch nicht möglich. Viel mehr soll "Strategic Closure" betrieben werden. Das bedeutet, dass Klassen so designt werden sollen, dass sie gegen bestimmte, zu erwartende künftige Änderungen geschützt sind. Wenn diese Änderungen an der Spezifikation tatsächlich eintreffen, sollen diese so umgesetzt werden können, dass die Klasse nicht geändert werden muss.
Vererbung
Als das Prinzip erstmals definiert wurde, war es hauptsächlich Vererbung, welche bei dessen Umsetzung zum Zug kam. Ein einfaches Beispiel sei mit folgendem Klassendiagramm gegeben:
Hier wird OCP auf die Klasse Calculator angewendet. Sie soll um Funktionalitäten wie die trigonometrischen Funktionen (sin(), cos(), tan()) ergänzt werden. Zudem könnte der ScientificCalculator die Wurzel-Funktion verbessern, indem er auch komplexe Zahlen als Ergebnis liefern kann. Statt jedoch die Klasse zu verändern, deren Funktionen bereits funktionieren, wird eine neue Klasse namens ScientificCalculator definiert, welche von Calculator erbt. Somit wurden diese neuen Features hinzugefügt, ohne bestehenden Code zu verändern.
Abstraktion
Ein moderner Ansatz ist die Abstraktion. Diese kann in zwei Fällen verwendet werden:
Wie oben erwähnt, ist es ohnehin nicht möglich, alle bestehenden Klassen so zu entwerfen, dass bei egal welcher Änderung an den Spezifikationen kein bestehender Quellcode angepasst werden muss. Stattdessen muss strategisch vorgegangen werden: Als Programmierer muss man überlegen, an welchen Stellen im Programm welche Arten von Änderungen zu einem späteren Zeitpunkt nötig werden könnten. An diesen Stellen sollte OCP angewendet werden, sodass diese Änderungen ohne Anpassung der bestehenden Klassen möglich sein werden.
Ein weiterer Anhaltspunkt, ob eine Klasse dem OCP unterworfen werden soll, also als nie-mehr-zu-ändern erklärt werden soll, ist, wie gross die Folgeschäden wären, wenn sie verändert werden würde. Dies ist in der Regel gleichbedeutend mit wie oft die Klasse im gesamten Programm verwendet wird. Denn falls viele andere Klassen von dieser Klasse abhängen könnte eine Änderung einen (ungewollten) Effekt auf grosse Teile des Programms haben. Deshalb wäre es dort sehr sinnvoll, OCP umzusetzen. Andere Klassen, welche sich am "Rand" der Applikation befinden und nur an wenigen Stellen im Code zum Einsatz kommen können ohne schlechtes Gewissen von OCP ausgenommen werden, da die Folgeschäden einer Änderung überschaubar sind.