- Don’t Repeat Yourself (DRY) – Wissenshäppchen #1
- You Ain’t Gonna Need It (YAGNI) – Wissenshäppchen #2
- Single Responsibility Principle (SRP) – Wissenshäppchen #3
- Open Closed Principle (OCP) – Wissenshäppchen #4
- Liskov Substitution Principle (LSP) – Wissenshäppchen #5
- Interface Segregation Principle (ISP) – Wissenshäppchen #6
- Dependency Inversion Principle (DIP) – Wissenshäppchen #7
- Law of Demeter (LoD) – Wissenshäppchen #8
Mein viertes Wissenshäppchen hat das Open Closed Principle zum Thema.
Podcast: Play in new window | Download (Duration: 22:17 — 10.6MB)
Abonnieren: Apple Podcasts | Spotify | RSS
Inhalt
Das OCP ist das zweite der SOLID-Prinzipien. Es wurde vor Robert „Uncle Bob“ Martin bereits 1988 von Bertrand Meyer definiert:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Ursprünglich ging es darum, durch Vererbung die Anpassung („modification“) bereits bestehender Klassen zu verhindern, indem Subklassen abgeleitet werden, die die vorhandene Funktionalität erweitern („extension“). Heutzutage ist Vererbung aber eher nicht mehr so gerne gesehen, da es eine starke Kopplung an die Basisklasse zur Folge hat, was das Design inflexibel macht. Stattdessen sollte man besser von vornherein Interfaces mit potentiell verschiedenen Implementierungen einsetzen.
Erklärung
- Bei Änderungen an einem Programm sollen bestehende Komponenten bestenfalls gar nicht angepasst werden müssen, da diese Anpassungen Fehler nach sich ziehen können, die auch das bereits bestehende Verhalten betreffen.
- Neue Funktionalität wird dem Programm immer durch neue Komponenten hinzugefügt, also z.B. indem neue Klassen oder Methoden ergänzt werden.
- Fallunterscheidungen auf Basis von Typen (z.B. mit
instanceof
in Java) oder Enumerations (insb. wenn diese in einemswitch
verwendet werden) sind oft ein Hinweis auf Verletzung des OCP und sollten durch Polymorphie ersetzt werden.
Beispiel
class Manager end class Developer end class Payroll def self.calculate_bonus(employee) if employee.is_a?(Manager) return 1000 end if employee.is_a?(Developer) return 500 end end end puts Payroll.calculate_bonus(Manager.new) # 1000 puts Payroll.calculate_bonus(Developer.new) #500
Wenn diese Bonusberechnung um einen neuen Mitarbeitertyp Administrator
erweitert werden soll, muss die bestehende Implementierung von calculate_bonus()
angepasst werden:
def self.calculate_bonus(employee) if employee.is_a?(Manager) return 1000 end if employee.is_a?(Developer) return 500 end if employee.is_a?(Administrator) return 250 end end
Stattdessen sollte von Anfang an die Berechnung des Bonuses an die Mitarbeiterklassen delegiert werden.
class Manager def bonus 1000 end end class Developer def bonus 500 end end class Payroll def self.calculate_bonus(employee) employee.bonus end end puts Payroll.calculate_bonus(Manager.new) # 1000 puts Payroll.calculate_bonus(Developer.new) # 500
Dadurch ist eine spätere Erweiterung um neue Mitarbeitertypen ohne Anpassung des bisherigen Codes möglich:
class Administrator def bonus 250 end end puts Payroll.calculate_bonus(Administrator.new) # 250
In dynamisch typisierten Sprachen (wie im Beispiel Ruby) ist das OCP übrigens meist noch einfacher umzusetzen als in statisch typisierten, da durch das Duck-Typing umfangreiche Klassenhierarchien oder Interfaces unnötig werden. Dies sieht man auch im Beispiel oben: Manager
und Developer
haben keinerlei Beziehung zueinander. Trotzdem funktioniert der Code, da beide Klassen die Methode bonus()
anbieten.
Vorteile
- Bei Erweiterungen der Funktionalität sinkt die Wahrscheinlichkeit, Fehler in den bereits funktionierenden Code einzubauen.
- Klassen behalten ihre Aufgabe „ein Leben lang“. Abhängige Klassen können sich darauf verlassen, dass sich die Funktionalität nicht ändert.
- Erweiterungen sind ggfs. einfacher zu implementieren, da nicht an mehreren Stellen Code angepasst, sondern „nur“ neuer Code geschrieben werden muss.
Nachteile
- Meist ist gar nicht im Voraus bekannt, welche Stellen im Code später einmal erweitert werden müssen. Einfach nur noch mit Abstraktionen zu arbeiten, macht den Code aber schwer verständlich. Außerdem verletzten wir damit das YAGNI-Prinzip.
Literaturempfehlungen
Da Uncle Bob die SOLID-Prinzipien so schön aufgeschrieben hat, kann ich wieder einmal seinen Klassiker Clean Code* empfehlen.
Links
- Permalink zu dieser Podcast-Episode
- RSS-Feed des Podcasts
- Open Closed Principle
- ArticleS.UncleBob.PrinciplesOfOod (Artikel von Uncle Bob, Beispiel in C++)
- Open-Closed-Prinzip – Wikipedia
- SOLID – Das Open Closed Principle | Informatik Aktuell (ausführliche Diskussion der Nachteile, Beispiel in C#)
- SOLID Design Principles Explained – The Open/Closed Principle with Code Examples (Beispiel in Java)
Die SOLID-Prinzipien wurden zwar von Uncle Bob aufgeschrieben, aber der Begriff stammt von Michael Feathers. 😉
Kann es sein, dass das OCP in einigen Situationen konträr zum SRP ist? Wenn ich bspw. eine
Angestellter
-Klasse habe, muss ich sie jetzt mit allen möglichen polymorphen Methoden versehen, z.B.getGehalt()
,getUrlaubstage()
etc. Somit führe ich doch wieder Verantwortung ein, die die Klasse nicht unbedingt haben sollte?Hallo Flo, ja das stimmt! Die Prinzipien alle gleichzeitig einzuhalten wird wohl schwierig bis unmöglich. Ich sehe sie auch nur als Richtlinie zur Orientierung beim eigenen Design. Je nach Anwendungsfall kann es ja auch sein, dass sich die Klassen so gut wie nie ändern oder erweitern werden. Dann sind auch die jeweiligen Prinzipien irrelevant.