Open/Closed Principle in Java with Example

Introduction

The Open/Closed Principle (OCP) is one of the five SOLID principles of object-oriented design. It states that software entities (such as classes, modules, and functions) should be open for extension but closed for modification. This means that the behavior of a module can be extended without modifying its source code.

Table of Contents

  1. What is the Open/Closed Principle?
  2. Benefits of the Open/Closed Principle
  3. Example: Violation of OCP
  4. Example: Adherence to OCP
  5. Real-World Example
  6. Conclusion

1. What is the Open/Closed Principle?

The Open/Closed Principle (OCP) asserts that a class should be open for extension but closed for modification. This means you should be able to add new functionality to a class by extending it, without changing the existing code.

2. Benefits of the Open/Closed Principle

  • Enhanced Maintainability: Reduces the risk of introducing bugs when adding new functionality.
  • Improved Flexibility: New features can be added without modifying existing code.
  • Increased Reusability: Classes that adhere to OCP are more likely to be reusable in different contexts.

3. Example: Violation of OCP

In this example, we'll create a Shape class and a AreaCalculator class that violates OCP by requiring modification to add new shapes.

Example:

class Circle {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}

class Rectangle {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }
}

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        }
        return 0;
    }
}

public class Main {
    public static void main(String[] args) {
        Circle circle = new Circle(5);
        Rectangle rectangle = new Rectangle(4, 5);

        AreaCalculator calculator = new AreaCalculator();
        System.out.println("Circle area: " + calculator.calculateArea(circle)); // Output: Circle area: 78.53981633974483
        System.out.println("Rectangle area: " + calculator.calculateArea(rectangle)); // Output: Rectangle area: 20.0
    }
}

Issues:

  • The AreaCalculator class must be modified to support new shapes, violating OCP.
  • Adding new shapes requires changes to the calculateArea method, making the code less maintainable.

4. Example: Adherence to OCP

To adhere to OCP, we can use polymorphism and interfaces to allow the AreaCalculator to work with any shape without modification.

Example:

Step 1: Define the Shape Interface

interface Shape {
    double calculateArea();
}

Step 2: Implement Specific Shape Classes

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

Step 3: Update the AreaCalculator Class

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

Step 4: Main Class to Demonstrate OCP

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 5);

        AreaCalculator calculator = new AreaCalculator();
        System.out.println("Circle area: " + calculator.calculateArea(circle)); // Output: Circle area: 78.53981633974483
        System.out.println("Rectangle area: " + calculator.calculateArea(rectangle)); // Output: Rectangle area: 20.0
    }
}

Explanation:

  • Shape: An interface that defines the calculateArea method.
  • Circle and Rectangle: Implementations of the Shape interface that provide specific area calculations.
  • AreaCalculator: A class that calculates the area of any Shape without needing modification.
  • Main: Demonstrates the use of OCP by adding new shapes without changing the AreaCalculator class.

5. Real-World Example

Example: Notification System

Consider a notification system where different types of notifications (e.g., email, SMS) need to be sent.

Step 1: Define the Notification Interface

interface Notification {
    void send(String message);
}

Step 2: Implement Specific Notification Classes

class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

Step 3: Update the NotificationService Class

class NotificationService {
    private List<Notification> notifications;

    public NotificationService() {
        this.notifications = new ArrayList<>();
    }

    public void addNotification(Notification notification) {
        notifications.add(notification);
    }

    public void sendAll(String message) {
        for (Notification notification : notifications) {
            notification.send(message);
        }
    }
}

Step 4: Main Class to Demonstrate OCP

public class Main {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();
        service.addNotification(new EmailNotification());
        service.addNotification(new SMSNotification());

        service.sendAll("Hello, World!");
        // Output:
        // Sending email: Hello, World!
        // Sending SMS: Hello, World!
    }
}

Explanation:

  • Notification: An interface that defines the send method.
  • EmailNotification and SMSNotification: Implementations of the Notification interface that provide specific sending mechanisms.
  • NotificationService: A class that sends notifications without needing modification for new types of notifications.
  • Main: Demonstrates the use of OCP by adding new notification types without changing the NotificationService class.

6. Conclusion

The Open/Closed Principle (OCP) is a fundamental concept in object-oriented design that promotes the extension of software entities without modifying their source code. By adhering to OCP, developers can create more maintainable, flexible, and reusable code. Understanding and applying OCP is essential for building robust and scalable Java applications.

Happy coding!

Comments

Post a Comment

Leave Comment