Skip to content

Open Closed

Definition

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

Plugin Architecture OCP


When to Apply OCP

When to Apply OCP


Tips & Tricks

OCP Tips