The Second SOLID Principle: Open/Closed Principle applied to java with spring boot

Daniel Angel
4 min readAug 4, 2023

--

The Open/Closed Principle is the second pillar of the SOLID principles in software engineering. Coined by Bertrand Meyer in 1988 and popularized by Robert C. Martin, this principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In other words, when changes or new functionalities are needed in a system, they should be implemented without altering the existing code.

Example in Java — Violation of the Open/Closed Principle:

Let’s assume we have a Calculator class that performs simple mathematical operations and contains a method for adding two numbers:

public class Calculadora {
public int sumar(int a, int b) {
return a + b;
}
}

Now, we are asked to add functionality to subtract two numbers. If we violate the Open/Closed Principle, we would directly modify the Calculator class by adding a new method:

public class Calculator {
public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}
}

This approach might work, but each time we need to add a new mathematical operation, we have to modify the Calculator class, which can be tedious and error-prone.

Example in Java — Applying the Open/Closed Principle:

To comply with the Open/Closed Principle, instead of modifying the Calculator class, we will create an interface called Operation that represents each mathematical operation:

public interface Operation {
int operate(int a, int b);
}

Then, we will create a class for each concrete mathematical operation, implementing the Operation interface. For example, for addition:

public class Addition implements Operation {
@Override
public int operate(int a, int b) {
return a + b;
}
}

And for subtraction:

public class Subtraction implements Operation {
@Override
public int operate(int a, int b) {
return a - b;
}
}

Finally, we can modify the Calculator class to accept any type of operation without altering its code:

public class Calculator {
public int operate(Operation operation, int a, int b) {
return operation.operate(a, b);
}
}

This way, we can add new operations without changing the Calculator class, This is one of the most used examples to explain in a simple way what the closed open principle consists of, now let’s see it a little more thoroughly.

Context: We are developing a RESTful API for a product management system in an online store. Currently, we have a Product class that handles basic information for each product, such as name, description, and price.

public class Product {
private String name;
private String description;
private double price;

// Getters and setters
}

Violation of the Open/Closed Principle: Suppose we are now asked to allow products to have discounts applied. If we violate the Open/Closed Principle, we might directly modify the Product class by adding a field for the discount and a method to calculate the discounted price:

public class Product {
private String name;
private String description;
private double price;
private double discount;

// Getters and setters

public double getDiscountedPrice() {
return price - discount;
}
}

Example of Applying the Open/Closed Principle: To comply with the Open/Closed Principle, instead of modifying the Product class, we will create an interface called Discount that represents the strategy for applying discounts:

public interface Discount {
double applyDiscount(double price);
}

Then, we will create classes for each concrete type of discount, implementing the Discount interface. For example, for a fixed discount:

public class FixedDiscount implements Discount {
private double amount;

public FixedDiscount(double amount) {
this.amount = amount;
}

@Override
public double applyDiscount(double price) {
return price - amount;
}
}

And for a percentage discount:

public class PercentageDiscount implements Discount {
private double percentage;

public PercentageDiscount(double percentage) {
this.percentage = percentage;
}

@Override
public double applyDiscount(double price) {
return price * (1 - percentage / 100);
}
}

Finally, we can use dependency injection in the Product class to apply the desired discount without modifying its code:

public class Product {
private String name;
private String description;
private double price;
private Discount discount;

// Getters and setters

public double getDiscountedPrice() {
return discount.applyDiscount(price);
}
}

This way, we can add new types of discounts without altering the Product class, following the Open/Closed Principle. For example:

Product product = new Product();
product.setPrice(100);
product.setDiscount(new FixedDiscount(10)); // Apply a fixed discount of 10
double discountedPrice = product.getDiscountedPrice(); // Returns 90

--------------------------

Product product = new Product();
product.setPrice(100);
product.setDiscount(new PercentageDiscount(20)); // Apply a percentage discount of 20%
double discountedPrice = product.getDiscountedPrice(); // Returns 80

In this manner, we comply with the Open/Closed Principle by extending the behavior of products to apply different discounts without modifying the Product class.

Best Practices:

  • Use interfaces or abstract classes to represent behavior that can change or be extended.
  • Apply the Dependency Injection principle to decouple classes that need to be extended from the classes that extend them.
  • Utilize design patterns like the Strategy pattern to implement different behavioral extensions.

Conclusion:

The Open/Closed Principle is essential for creating robust and maintainable systems. By following this principle, we encourage the use of extension instead of modification, avoiding issues such as introducing errors into existing code and enabling the incorporation of new features without altering tested functionality. By using interfaces, abstract classes, and appropriate design patterns, we can design more flexible and scalable software, resulting in more efficient and sustainable development in the long run.

--

--

Daniel Angel
Daniel Angel

Written by Daniel Angel

Backend developer | Java, Spring boot, Cloud| Laravel, Php

No responses yet