Dezign Patterns


Open/Closed Principle (OCP)

📜 Definition : Software entities (classes, modules, functions) should be open for extension, but closed for modification.
🎯 Intent : You should be able to add new features without changing existing code.

In the below example we say that the Circle and Square classes are closed for modification, Shape interface is open for extension.


abstract class Shape {
    abstract double area();
}

class Circle extends Shape {
    double radius;
    public double area() { return Math.PI * radius * radius; }
}

class Square extends Shape {
    double side;
    public double area() { return side * side; }
}
Now we can add new shapes[Rectangle, Triangle] without modifying existing ones.

❌ Open Close Principle Violation Example

🧠 General Rule of Thumb:
If your class has a lot of if-else or switch statements or keeps changing as you add features, it's probably violating OCP. 👀

❌ 1. If-Else Chains for Type Checking

class PaymentProcessor {
public void processPayment(String type) {
    if (type.equals("CREDIT")) {
        // credit card logic
    } else if (type.equals("PAYPAL")) {
        // PayPal logic
    } else if (type.equals("BITCOIN")) {
        // Bitcoin logic
    }
}
}
✅ Fix: Use polymorphism + Strategy pattern — create a PaymentMethod interface with different implementations for each type.
public interface PaymentMethod {
    void processPayment();
}
public class CreditCardPayment implements PaymentMethod {
    @Override
    public void processPayment() {
        System.out.println("Processing Credit Card Payment...");
        // Credit card logic
    }
}

 public class PayPalPayment implements PaymentMethod {
    @Override
    public void processPayment() {
        System.out.println("Processing PayPal Payment...");
        // PayPal logic
    }
}

 public class BitcoinPayment implements PaymentMethod {
    @Override
    public void processPayment() {
        System.out.println("Processing Bitcoin Payment...");
        // Bitcoin logic
    }
}

 // Strtegy Context
public class PaymentProcessor {
    private PaymentMethod paymentMethod;

     // Constructor that accepts a PaymentMethod
    public PaymentProcessor(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

     // Delegate payment processing to the PaymentMethod
    public void processPayment() {
        paymentMethod.processPayment();
    }
}
Client code
public class Main {
    public static void main(String[] args) {

         // Choose payment method dynamically
        PaymentMethod creditCardPayment = new CreditCardPayment();
        PaymentMethod paypalPayment = new PayPalPayment();
        PaymentMethod bitcoinPayment = new BitcoinPayment();

         // Create PaymentProcessor with different payment methods
        PaymentProcessor processor = new PaymentProcessor(creditCardPayment);
        processor.processPayment(); // Outputs: Processing Credit Card Payment...

         processor = new PaymentProcessor(paypalPayment);
        processor.processPayment(); // Outputs: Processing PayPal Payment...

         processor = new PaymentProcessor(bitcoinPayment);
        processor.processPayment(); // Outputs: Processing Bitcoin Payment...
    }
}

❌ 2. Switch Statements on Enum or String


enum ShapeType { CIRCLE, RECTANGLE }
class AreaCalculator {
    public double calculateArea(ShapeType shapeType, double value) {
        switch (shapeType) {
            case CIRCLE: return Math.PI * value * value;
            case RECTANGLE: return value * value; // "PRETEND both sides are same"
            default: return 0;
        }
    }
}
✅ Fix: Use inheritance or polymorphism. Create a Shape interface with an area() method.

❌ 3. Hard-Coded Logic in Classes

class ReportService {
public void generateReport(String format) {
    if (format.equals("PDF")) {
        // generate PDF
    } else if (format.equals("EXCEL")) {
        // generate Excel
    }
}
}
✅ Fix: Extract a ReportGenerator interface and inject format-specific generators (via Factory or Strategy pattern).

❌ 4. Adding Features with Boolean Flags

class Notification {
boolean isEmail;
boolean isSMS;

public void send(String message) {
    if (isEmail) {
        // send email
    }
    if (isSMS) {
        // send SMS
    }
}
}
✅ Fix: Again, use polymorphism — maybe a NotificationChannel interface.

❌ 5. Logging Systems with Manual Control

class Logger {
public void log(String level, String message) {
    if (level.equals("INFO")) {
        // log info
    } else if (level.equals("ERROR")) {
        // log error
    }
}
}
✅ Fix: Define Logger as an interface and implement ConsoleLogger, FileLogger, DBLogger, etc.