โ Back to OOP Concepts
๐
Principles
Open/Closed
Software entities should be open for extension but closed for modification โ add new behavior without changing existing, tested code.
The Problem
When new requirements are handled by editing existing classes โ e.g., adding another `if` branch for each new type โ every change risks breaking existing functionality and requires re-testing the whole class.
How It Works
- โบNew behavior is added by creating new classes/implementations, not by editing existing ones
- โบAchieved through abstraction โ interfaces or protocols that existing code depends on
- โบExisting, tested code remains untouched and stable
- โบOften realized via the Strategy pattern, polymorphism, or plugin architectures
When to Use
โSystems that frequently need new variants (payment methods, file exporters, discount rules)
โAvoiding long if/else or switch chains that grow with each new type
โDesigning plugin-based architectures
Real-World Examples
๐
iOS
Adding a new UIView subclass without modifying UIKit itself
๐ค
Android
Adding a new PaymentMethod implementation without touching the checkout flow
๐
Web
Adding a new export format by implementing an Exporter interface
Code Examples
Swift
protocol DiscountStrategy {
func apply(to price: Double) -> Double
}
struct NoDiscount: DiscountStrategy {
func apply(to price: Double) -> Double { price }
}
struct PercentageDiscount: DiscountStrategy {
let percent: Double
func apply(to price: Double) -> Double { price * (1 - percent / 100) }
}
// New discount types are added without modifying PriceCalculator
struct PriceCalculator {
let strategy: DiscountStrategy
func finalPrice(for price: Double) -> Double {
strategy.apply(to: price)
}
}
let calculator = PriceCalculator(strategy: PercentageDiscount(percent: 20))
print(calculator.finalPrice(for: 100)) // 80.0