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.
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.
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);
}
}
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);
}
}
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.
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);
}
}
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.