OCP (Open/Closed Principle) ist das zweite Prinzip von SOLID, das besagt: Software-Entitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein. Dieses Prinzip, formuliert von Bertrand Meyer im Jahr 1988, ermöglicht das Hinzufügen neuer Funktionalität ohne Änderung vorhandenen Codes. Laut Robert Martins Buch Clean Architecture (2017) wird das Offenheitsprinzip durch Abstraktionen und Polymorphismus implementiert, wodurch das Risiko von Regressionen minimiert wird.
Wichtige Punkte
OCP (Open/Closed Principle) — das Prinzip der Offenheit für Erweiterung und Geschlossenheit für Modifikation. Klassen, Module und Funktionen sollten so entworfen werden, dass neues Verhalten hinzugefügt werden kann, ohne ihren Quellcode zu ändern. Erweiterung wird durch Vererbung, Komposition oder Austausch von Schnittstellenimplementierungen erreicht.
Bertrand Meyer beschrieb in seinem Buch Object-Oriented Software Construction (1988) erstmals OCP durch Vererbung: die Basisklasse bleibt unverändert, während Unterklassen ihr Verhalten erweitern. Die moderne Interpretation von OCP, vorgeschlagen von Robert Martin, basiert auf Polymorphismus und Schnittstellen: anstelle von Vererbung werden abstrakte Verträge verwendet.
Der Unterschied zwischen den Ansätzen ist erheblich. Vererbung erzeugt eine enge Kopplung zwischen Basis- und abgeleiteten Klassen. Schnittstellen und Komposition bieten Flexibilität: die Implementierung kann ausgetauscht werden, ohne den Client-Code zu ändern. Modernes OCP dreht sich um Abstraktion, nicht um Vererbung.
Polymorphes OCP verwendet abstrakte Klassen oder Schnittstellen, um einen Vertrag zu definieren. Der Client-Code arbeitet mit der Abstraktion, ohne die konkrete Implementierung zu kennen. Neue Funktionalität wird durch Erstellen einer neuen Klasse hinzugefügt, die dieselbe Schnittstelle implementiert — ohne eine einzige Änderung am vorhandenen Code. Dies macht das System widerstandsfähig gegen Änderungen und vorhersagbar für Erweiterungen.
In der mobilen Entwicklung ist dieser Ansatz allgegenwärtig: das Strategy-Muster ermöglicht den Austausch von Algorithmen (Bildkomprimierung, Caching, Authentifizierung) über eine gemeinsame Schnittstelle. Das Hinzufügen einer neuen Strategie erfordert keine Änderung des Codes, der sie verwendet.
OCP implementieren beginnt damit, veränderliches Verhalten in eine Abstraktion zu isolieren. Wenn es im Code eine switch-Konstruktion oder eine if-else-Kette gibt, die den Typ eines Objekts prüft — ist das ein Signal, OCP anzuwenden. Jeder bedingte Zweig erfordert potenziell das Hinzufügen eines neuen Zweigs bei Erweiterung.
Der Refactoring-Prozess unter OCP umfasst drei Schritte: den veränderlichen Aspekt identifizieren (was erweitert werden kann), ihn in eine Schnittstelle oder abstrakte Klasse isolieren, den Client-Code umschreiben, um mit der Abstraktion statt der konkreten Klasse zu arbeiten. Danach wird neue Funktionalität ohne Änderung des Clients hinzugefügt.
Eine wichtige Klarstellung: Geschlossenheit für Modifikation ist nicht absolut. Wenn eine Anforderungsänderung die Abstraktion selbst oder den Vertrag betrifft — ist die Änderung unvermeidlich. OCP schützt vor Änderungen in Implementierungen, nicht in Verträgen. Gutes Design geht davon aus, dass Verträge stabil und Implementierungen variabel sind.
Bei der Bewertung der OCP-Kompatibilität einer Architektur ist es nützlich, auf Erweiterungspunkte zu achten. Jeder Punkt, an dem ein Entwickler if-else oder switch für einen neuen Typ hinzufügt — ist ein Kandidat für Abstraktion. Ein nach OCP entworfenes System hat vorhersagbare Erweiterungspunkte: Schnittstellen mit Dokumentation, die besagt „implementiere diese Schnittstelle, um einen neuen Typ hinzuzufügen“. In Android ist das Factory-Muster mit ViewModelProvider.Factory ein klares Beispiel — das Hinzufügen eines neuen ViewModel-Typs erfordert keine Änderung vorhandener Fabriken.
Die effektivsten Muster für die Einhaltung von OCP in der mobilen Entwicklung umfassen Strategy, Template Method, Decorator und Factory. Jedes löst das Problem der Verhaltenserweiterung ohne Änderung vorhandenen Codes durch unterschiedliche objektorientierte Entwurfsmechanismen.
Strategy ermöglicht den Austausch von Algorithmen im laufenden Betrieb über eine gemeinsame Schnittstelle. In der iOS-Entwicklung werden Strategien für Animationen und Formularvalidierung verwendet. Template Method definiert das Skelett eines Algorithmus in einer Basisklasse, und Unterklassen überschreiben die Schritte — geeignet für Bildschirme mit gemeinsamer Struktur, aber unterschiedlichem Inhalt.
Decorator fügt dynamisch Verhalten zu einem Objekt hinzu, ohne seine Klasse zu ändern. In Android wird Decorator verwendet, um ein Repository mit einer Caching- oder Logging-Schicht zu umhüllen. Factory Method erstellt Objekte über eine Schnittstelle, sodass Unterklassen entscheiden können, welche Klasse instanziiert werden soll — die Grundlage OCP-kompatibler Abhängigkeitserstellung.
Die Musterauswahl hängt von der Stabilität des erweiterten Verhaltens ab. Strategy ist optimal, wenn Algorithmen vollständig ersetzt werden. Template Method — wenn die Struktur festgelegt ist, aber die Schritte variieren. Decorator — wenn die Erweiterung für den Client transparent sein soll. Für die meisten Szenarien in Android und iOS ist Strategy + Dependency Injection ausreichend.
Die Anwendung dieser Muster ohne OCP ist technisch möglich, verliert aber ihren Sinn. Es ist OCP, das rechtfertigt, warum wir eine zusätzliche Abstraktionsebene einführen: damit das System wachsen kann, ohne vorhandenen Code neu zu schreiben.
Betrachten wir ein Android-Beispiel mit Zahlungsabwicklung. Ohne OCP erfordert jedes neue Zahlungssystem Änderungen an der Handler-Klasse. Mit OCP wird eine neue Schnittstellenimplementierung hinzugefügt, ohne vorhandenen Code zu ändern.
// OCP-Verletzung: switch erfordert Änderung beim Hinzufügen eines neuen Systems
class BadPaymentProcessor {
fun process(type: String) {
when (type) {
"card" -> // Kartenverarbeitung
"paypal" -> // PayPal-Verarbeitung
}
}
}
// OCP-kompatibles Design
interface PaymentMethod {
fun pay(amount: Double)
}
class CardPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
class PayPalPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
// Neues System — neue Klasse, ohne Änderung vorhandenen Codes
class ApplePayPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
Ein iOS-Beispiel mit Textfeldvalidierung demonstriert dieselbe Logik durch Swift-Protokolle:
// OCP-kompatible Validierung
protocol ValidationRule {
func validate(_ input: String) -> Bool
}
struct EmailRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.contains("@")
}
}
struct PhoneRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count == 11
}
}
// Das Hinzufügen einer neuen Regel erfordert keine Änderung des Validator-Codes
struct PasswordRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count >= 8
}
}
Der Hauptvorteil von OCP in diesen Beispielen: das Hinzufügen von ApplePay oder PasswordRule erfordert keine Änderung vorhandener Klassen. Der Code erweitert sich horizontal — durch neue Dateien, nicht durch Änderung alter. Dies reduziert das Regressionsrisiko und beschleunigt die Implementierung neuer Funktionalität.
Die häufigste Verletzung ist eine switch- oder when-Konstruktion basierend auf dem Objekttyp. Jedes Mal, wenn ein neuer Typ hinzugefügt wird, müssen alle solchen switch-Anweisungen im Code gefunden und ein neuer Zweig hinzugefügt werden. Ein übersehener switch ist ein Laufzeitfehler, der zur Kompilierzeit schwer zu erkennen ist.
In der mobilen Entwicklung wird OCP bei der Verwendung von riesigen enum-Klassen mit Methoden, die vom enum-Wert abhängen, verletzt. Das Hinzufügen eines neuen enum-Elements erfordert die Änderung jedes switch im gesamten Projekt. Die Alternative ist Polymorphismus über eine Schnittstelle, bei der jeder Typ sein eigenes Verhalten implementiert.
Eine weitere typische Verletzung ist der God Adapter: RecyclerView.Adapter (Android) oder UITableViewDataSource (iOS), der verschiedene Zelltypen durch if-else behandelt. Jeder neue Zelltyp erfordert eine Erweiterung des Adapters. Die Lösung ist ein polymorpher ViewHolder mit einer gemeinsamen bind-Methode, bei der jeder Zelltyp für seine eigene Darstellung verantwortlich ist.
Vorbeugende Maßnahmen umfassen: Vermeidung von typbasiertem switch zugunsten von Polymorphismus, Injektion von Abhängigkeiten über Schnittstellen und Verwendung des Factory-Musters zur Objekterstellung gemäß Konfiguration. Die Analyse des Codes auf „Typschalter“ ist ein obligatorischer Teil der Code-Überprüfung in OCP-orientierten Teams.
Refactoring einer bestehenden OCP-Verletzung erfolgt durch Replace Conditional with Polymorphism: jeder bedingte Zweig wird zu einer separaten Klasse, die eine gemeinsame Schnittstelle implementiert. Der Client-Code wird umgeschrieben, um mit der Schnittstelle zu arbeiten, und die konkrete Implementierung wird über eine Factory oder einen DI-Container bereitgestellt.
Es ist wichtig zu verstehen, dass OCP und Polymorphismus nicht alle Erweiterungsprobleme lösen. Wenn die Architektur falsch gewählt ist, erfordert das Hinzufügen neuer Funktionalität die Änderung nicht nur von Implementierungen, sondern auch von Verträgen. Gute Architektur sagt Erweiterungsrichtungen voraus und platziert Abstraktionen genau an diesen Punkten. Investitionen in OCP zahlen sich umso mehr aus, je länger das Projekt lebt und je häufiger sich die Anforderungen an bestimmte Module ändern.
Häufig gestellte Fragen
Nein. OCP verbietet die Änderung vorhandenen Codes beim Hinzufügen neuer Funktionalität, die sich auf dieselbe Abstraktion bezieht. Die Änderung eines Vertrags, die Behebung von Fehlern und Refactoring sind keine OCP-Verletzungen — das Prinzip schützt vor kaskadenartigen Änderungen bei Erweiterungen.
Strategy ist eine direkte Implementierung von OCP. Die Strategie-Schnittstelle definiert den Vertrag, der Client hängt von der Abstraktion ab, und konkrete Strategien implementieren variables Verhalten. Das Hinzufügen einer neuen Strategie erfordert keine Änderung des Clients — das ist Offenheit für Erweiterung bei Geschlossenheit für Modifikation.
Ja, durch Vererbung und Template Method: die Basisklasse definiert das Algorithmusskelett, und Unterklassen überschreiben die Schritte. Allerdings erzeugt Vererbung eine enge Kopplung und ist weniger flexibel als Schnittstellen. In der modernen Entwicklung gelten Schnittstellen und Komposition als bevorzugte Methode zur Implementierung von OCP.
OCP-kompatibler Code vereinfacht das Testen: jede Schnittstellenimplementierung wird isoliert getestet. Der Client-Code wird mit einer Mock-Implementierung getestet, was die Überprüfung der Logik ohne Bindung an ein bestimmtes Verhalten ermöglicht. Die Erweiterung des Systems erfordert kein Umschreiben vorhandener Tests.
Nein. OCP ist gerechtfertigt, wenn funktionale Erweiterung vorhersagbar ist. Für stabilen Code, der nicht erweitert werden soll, ist zusätzliche Abstraktion übermäßig. YAGNI (You Ain't Gonna Need It) ist ein guter Gegenpol zu OCP: Abstraktion wird eingeführt, wenn eine zweite Verhaltensvariante auftaucht, nicht präventiv.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch