Open/Closed Principle (OCP)
What is the Open/Closed Principle?​
Shubh: Hey, do you know about the Open/Closed Principle?
You: I've heard of it, but I'm not entirely sure how it applies to coding. Can you explain it?
Shubh: Of course! The Open/Closed Principle (OCP) is one of the SOLID principles of object-oriented design. It states that software entities like classes, modules, and functions should be open for extension but closed for modification. This means you should be able to add new functionality without changing existing code.
Understanding the Problem: A Bad Approach​
To better understand the Open/Closed Principle, let's look at an example where this principle is not followed.
Shubh: Here’s a Payment
class that violates the OCP because it requires modification every time a new payment method is added:
public class Payment {
public void pay(PaymentType paymentType){
if(paymentType == PaymentType.PAYPAL){
System.out.println("Paying through paypal");
}else if(paymentType == PaymentType.PAYTM){
System.out.println("Paying through paytm");
}else if(paymentType == PaymentType.GOOGLEPAY){
System.out.println("Paying through google pay");
}
}
}
public enum PaymentType {
PAYPAL, PAYTM, GOOGLEPAY, STRIPE;
}
public class Main {
public static void main(String[] args) {
Payment paymentObj = new Payment();
paymentObj.pay(PaymentType.GOOGLEPAY);
}
}
You: I see. The Payment
class has to be modified every time we add a new payment method. This violates the Open/Closed Principle.
Shubh: Exactly! This makes the code harder to maintain and more prone to errors. We should avoid modifying the existing Payment
class to add new payment methods. Instead, we should design it in a way that allows us to extend its functionality without changing the existing code.
A Better Approach: Applying the Open/Closed Principle​
To adhere to the Open/Closed Principle, we can refactor the code using polymorphism, allowing us to add new payment methods without modifying existing code.
You: How can we refactor this to follow the OCP?
Shubh: Let’s do that by creating an interface for payment methods and then implementing it for each payment type:
interface PaymentMethod {
void pay();
}
class PaypalPayment implements PaymentMethod {
@Override
public void pay() {
System.out.println("Paying through PayPal");
}
}
class PaytmPayment implements PaymentMethod {
@Override
public void pay() {
System.out.println("Paying through Paytm");
}
}
class GooglePayPayment implements PaymentMethod {
@Override
public void pay() {
System.out.println("Paying through Google Pay");
}
}
// no need to edit if new payment methods are needed
public class Payment {
private final PaymentMethod paymentMethod;
public Payment(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void pay() {
paymentMethod.pay();
}
}
public class Main {
public static void main(String[] args) {
Payment paymentObj = new Payment(new GooglePayPayment());
paymentObj.pay();
}
}
Shubh: In this refactored code, we have an interface PaymentMethod
and different implementations for each payment type. The Payment
class now takes a PaymentMethod
object and calls its pay method.
You: I see. Now, if we want to add a new payment method, we just create a new class implementing the PaymentMethod
interface, and we don't need to modify the Payment
class.
Shubh: Exactly! This adheres to the Open/Closed Principle. Our Payment class is now closed for modification but open for extension.
You: This is much cleaner and easier to maintain. Thanks for explaining the Open/Closed Principle with such a clear example!