Sobes.tech
Middle+

How do you apply the open-closed principle from SOLID in your designs?

sobes.tech AI

Answer from AI

I apply the Open/Closed Principle (OCP), focusing on extendability without modifying existing code.

Key approaches:

  • Using abstractions (interfaces and abstract classes): My code depends on abstractions rather than concrete implementations. This allows adding new implementations without changing existing code that uses these abstractions.

    // Interface representing a contract
    public interface PaymentProcessor {
        void processPayment(BigDecimal amount);
    }
    
    // Concrete implementation
    public class CreditCardPaymentProcessor implements PaymentProcessor {
        @Override
        public void processPayment(BigDecimal amount) {
            // Credit card processing logic
            System.out.println("Processing credit card payment: " + amount);
        }
    }
    
    // Another implementation
    public class PayPalPaymentProcessor implements PaymentProcessor {
        @Override
        public void processPayment(BigDecimal amount) {
            // PayPal processing logic
            System.out.println("Processing PayPal payment: " + amount);
        }
    }
    
    // Class using abstraction, closed for modification but open for extension with new PaymentProcessors
    public class Order {
        private PaymentProcessor paymentProcessor;
    
        public Order(PaymentProcessor paymentProcessor) {
            this.paymentProcessor = paymentProcessor;
        }
    
        public void finalizeOrder(BigDecimal totalAmount) {
            paymentProcessor.processPayment(totalAmount);
            // Additional order logic
        }
    }
    
  • Strategy Pattern: This design pattern is a direct embodiment of OCP. It allows defining a family of algorithms, encapsulating each one, and making them interchangeable.

    // Strategy interface
    interface ShippingStrategy {
        BigDecimal calculateCost(BigDecimal weight);
    }
    
    // Concrete strategy
    class StandardShipping implements ShippingStrategy {
        @Override
        public BigDecimal calculateCost(BigDecimal weight) {
            return weight.multiply(new BigDecimal("2.5"));
        }
    }
    
    // Another strategy
    class ExpressShipping implements ShippingStrategy {
        @Override
        public BigDecimal calculateCost(BigDecimal weight) {
            return weight.multiply(new BigDecimal("5.0")).add(new BigDecimal("10.0"));
        }
    }
    
    // Context using strategy, closed for modification but open for new strategies
    class Order {
        private ShippingStrategy shippingStrategy;
    
        public Order(ShippingStrategy shippingStrategy) {
            this.shippingStrategy = shippingStrategy;
        }
    
        public BigDecimal getShippingCost(BigDecimal weight) {
            return shippingStrategy.calculateCost(weight);
        }
    }
    
  • Template Method Pattern: Defines the skeleton of an algorithm in a superclass, leaving details to subclasses. This allows changing steps of the algorithm without altering its structure.

    // Abstract class with template method
    abstract class DataProcessor {
        // Template method
        public final void processData() {
            readData();
            transformData();
            writeData();
        }
    
        // Abstract methods to be overridden in subclasses
        abstract void readData();
        abstract void transformData();
        abstract void writeData();
    }
    
    // Concrete processor extending the template
    class CSVDataProcessor extends DataProcessor {
        @Override
        void readData() {
            System.out.println("Reading data from CSV...");
        }
    
        @Override
        void transformData() {
            System.out.println("Transforming CSV data...");
        }
    
        @Override
        void writeData() {
            System.out.println("Writing data to database...");
        }
    }
    
  • Factory Methods: Allow creating objects by providing an interface for creation but leaving the choice of concrete class to subclasses. This adds flexibility in object creation without changing the client code that uses the factory.

    interface Product {
        void use();
    }
    
    class ConcreteProductA implements Product {
        @Override
        public void use() {
            System.out.println("Using Product A");
        }
    }
    
    class ConcreteProductB implements Product {
        @Override
        public void use() {
            System.out.println("Using Product B");
        }
    }
    
    abstract class Creator {
        public void operation() {
            Product product = factoryMethod();
            product.use();
        }
    
        // Factory method
        protected abstract Product factoryMethod();
    }
    
    class ConcreteCreatorA extends Creator {
        @Override
        protected Product factoryMethod() {
            return new ConcreteProductA();
        }
    }
    
    class ConcreteCreatorB extends Creator {
        @Override
        protected Product factoryMethod() {
            return new ConcreteProductB();
        }
    }
    

By applying these approaches, I create code that is easier to maintain, extend, and test, minimizing the risk of errors when adding new functionality.

How do you apply the open-closed principle from SOLID… - sobes.tech