Open Closed
Definition

Violation Example
// BAD: Must modify existing code for each new shape
public class AreaCalculator {
public double calculateArea(Object shape) {
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.width * r.height;
}
else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return Math.PI * c.radius * c.radius;
}
else if (shape instanceof Triangle) {
// New shape added - must modify this class!
Triangle t = (Triangle) shape;
return 0.5 * t.base * t.height;
}
// Every new shape requires modifying this method!
throw new IllegalArgumentException("Unknown shape");
}
}
// Problems:
// - Adding new shape requires changing AreaCalculator
// - Risk of breaking existing calculations
// - This class grows indefinitely
// - Violates Single Responsibility too
Proper Implementation
// GOOD: Open for extension, closed for modification
// Abstraction that defines the contract
public interface Shape {
double calculateArea();
}
// Each shape implements its own area calculation
public class Rectangle implements Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
public class Circle implements Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
// Calculator is CLOSED for modification
public class AreaCalculator {
public double calculateTotalArea(List<Shape> shapes) {
return shapes.stream()
.mapToDouble(Shape::calculateArea)
.sum();
}
}
// Adding new shape - NO changes to existing code!
public class Hexagon implements Shape {
private final double side;
public Hexagon(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return (3 * Math.sqrt(3) / 2) * side * side;
}
}
// AreaCalculator works with Hexagon without any modification!
Strategy Pattern (OCP in Action)
// Payment system using Strategy Pattern
// Strategy interface
public interface PaymentStrategy {
PaymentResult process(Money amount, PaymentDetails details);
boolean supports(PaymentMethod method);
}
// Concrete strategies
public class CreditCardPayment implements PaymentStrategy {
public PaymentResult process(Money amount, PaymentDetails details) {
// Credit card processing logic
}
public boolean supports(PaymentMethod method) {
return method == PaymentMethod.CREDIT_CARD;
}
}
public class PayPalPayment implements PaymentStrategy {
public PaymentResult process(Money amount, PaymentDetails details) {
// PayPal processing logic
}
public boolean supports(PaymentMethod method) {
return method == PaymentMethod.PAYPAL;
}
}
// Payment processor - CLOSED for modification
public class PaymentProcessor {
private final List<PaymentStrategy> strategies;
public PaymentProcessor(List<PaymentStrategy> strategies) {
this.strategies = strategies;
}
public PaymentResult process(PaymentRequest request) {
return strategies.stream()
.filter(s -> s.supports(request.getMethod()))
.findFirst()
.orElseThrow(() -> new UnsupportedPaymentException())
.process(request.getAmount(), request.getDetails());
}
}
// Adding Apple Pay - just add new strategy!
public class ApplePayPayment implements PaymentStrategy {
// Implementation...
}
// Wire in configuration - PaymentProcessor unchanged
@Configuration
public class PaymentConfig {
@Bean
public PaymentProcessor paymentProcessor() {
return new PaymentProcessor(List.of(
new CreditCardPayment(),
new PayPalPayment(),
new ApplePayPayment() // Just add here!
));
}
}
Decorator Pattern (OCP in Action)
// Extending behavior without modifying original
// Base interface
public interface DataSource {
void writeData(String data);
String readData();
}
// Concrete implementation
public class FileDataSource implements DataSource {
private final String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
public void writeData(String data) {
// Write to file
}
public String readData() {
// Read from file
}
}
// Decorator base
public abstract class DataSourceDecorator implements DataSource {
protected final DataSource wrapped;
public DataSourceDecorator(DataSource source) {
this.wrapped = source;
}
}
// Add encryption without modifying FileDataSource
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
public void writeData(String data) {
wrapped.writeData(encrypt(data));
}
public String readData() {
return decrypt(wrapped.readData());
}
private String encrypt(String data) { /* ... */ }
private String decrypt(String data) { /* ... */ }
}
// Add compression without modifying anything
public class CompressionDecorator extends DataSourceDecorator {
public void writeData(String data) {
wrapped.writeData(compress(data));
}
public String readData() {
return decompress(wrapped.readData());
}
}
// Usage - combine behaviors!
DataSource source = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("data.txt")
)
);
Plugin Architecture

When to Apply OCP

Tips & Tricks
