Open/Closed Principle (OCP)


The Open/Closed Principle (OCP)is a foundational concept in object-oriented programming (OOP) that states:
"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.".

Modifying existing code can introduce new bugs and increase the cost of testing and maintaining the system. Therefore, the OCP ensures the system remains robust, maintainable, and flexible as new requirements are introduced.

Example: Notification System

Imagine you are designing a notification system for a messaging platform. Initially, the system only supports email notifications. Later, you might want to add support for SMS notifications and, eventually, for push notifications.

Violation of OCP:

Consider the initial implementation where the notification system is limited to email notifications.


        class NotificationService {
            public void sendNotification(String message, String type) {
                if (type.equals("email")) {
                    sendEmail(message);
                }
            }
        
            private void sendEmail(String message) {
                System.out.println("Sending email: " + message);
            }
        }        
    

Problem (OCP Violation):

For example, adding SMS notification support would require changing the class like this:


        class NotificationService {
            public void sendNotification(String message, String type) {
                if (type.equals("email")) {
                    sendEmail(message);
                } else if (type.equals("sms")) {
                    sendSMS(message);
                }
            }

            private void sendEmail(String message) {
                System.out.println("Sending email: " + message);
            }

            private void sendSMS(String message) {
                System.out.println("Sending SMS: " + message);
            }
        }
        

This violates OCP because:
  1. Every time a new notification type is added, we need to modify the existing code.
  2. Adding new notification types becomes cumbersome and can introduce bugs if other parts of the method are accidentally affected.

How to Achieve OCP

To adhere to the Open/Closed Principle, we can refactor the design by using polymorphism and interfaces. Instead of using conditionals (if-else or switch statements) to check the type of notification, we can define a common interface for all notification types and implement specific behaviors in separate classes.
This way, the NotificationService class doesn’t need to be modified when we introduce new notification types — it simply works with the interface.


Refactored Design (Complying with OCP):

We can create an interface Notification and then implement different notification types like EmailNotification, SmsNotification, and PushNotification.


        // Notification Interface
        interface Notification {
            void send(String message);
        }
        
        // Email Notification Class
        class EmailNotification implements Notification {
            @Override
            public void send(String message) {
                System.out.println("Sending email: " + message);
            }
        }
        
        // SMS Notification Class
        class SmsNotification implements Notification {
            @Override
            public void send(String message) {
                System.out.println("Sending SMS: " + message);
            }
        }
        
        // Push Notification Class (New Notification Type)
        class PushNotification implements Notification {
            @Override
            public void send(String message) {
                System.out.println("Sending Push Notification: " + message);
            }
        }
        
        // Notification Service that is Open for Extension but Closed for Modification
        class NotificationService {
            private final Notification notification;
        
            public NotificationService(Notification notification) {
                this.notification = notification;
            }
        
            public void sendNotification(String message) {
                notification.send(message);
            }
        }
        
    
How It Now Complies with OCP:
  1. The NotificationService class is closed for modification: It no longer changes when new types of notifications are introduced.
  2. The system is open for extension: You can add new notification types by implementing the Notification interface in a new class, without altering existing code.

Conclusion

In the original design, adding new types of notifications (like SMS or Push) required modifying the existing NotificationService class, which violated the Open/Closed Principle.
By refactoring the code and introducing an interface (Notification) with concrete implementations for each notification type (EmailNotification, SmsNotification, etc.), we now have a design that is open for extension (new notification types) but closed for modification (no changes required in existing code).

This approach makes the system more flexible and maintainable as new requirements are introduced, ensuring that changes in one part of the system don't ripple through and cause issues in other parts.