- Last updated: 22 Jan, 2025
The SOLID principles are a set of five design principles that guide object-oriented programming and design. They help developers create systems that are easy to maintain, scale, and extend. These principles are particularly useful for writing clean and maintainable code in Java. It was Conceptualized by Robert C. Martin, also known as Uncle Bob. Five Design principles of SOLID principles,
Single Responsibility Principle (SRP):
A class should have only one reason to change, meaning it should perform a single responsibility or task.
Example, Without SRP,
public class UserManager { public void addUser(User user) { // Add user logic } public void validateUser(User user) { // Validate user logic } public void sendWelcomeEmail(User user) { // Email sending logic } }
With SRP,
public class UserManager { public void addUser(User user) { // Add user logic } } public class UserValidator { public void validateUser(User user) { // Validation logic } } public class EmailService { public void sendWelcomeEmail(User user) { // Email sending logic } }
Here, the UserManager, UserValidator, and EmailService each have a single responsibility.
Open/Closed Principle (OCP):
Software entities (classes, modules, functions) should be open for extension but closed for modification.
Without OCP,
public class PaymentService { public void processPayment(String paymentType) { if (paymentType.equals("CREDIT_CARD")) { // Credit card processing } else if (paymentType.equals("PAYPAL")) { // PayPal processing } } }
With OCP,
public interface PaymentProcessor { void processPayment(); } public class CreditCardProcessor implements PaymentProcessor { @Override public void processPayment() { // Credit card processing } } public class PayPalProcessor implements PaymentProcessor { @Override public void processPayment() { // PayPal processing } } public class PaymentService { private final Listprocessors; public PaymentService(List processors) { this.processors = processors; } public void processPayments() { for (PaymentProcessor processor : processors) { processor.processPayment(); } } }
New payment types can be added without modifying PaymentService.
Liskov Substitution Principle (LSP):
Subtypes must be substitutable for their base types without altering the correctness of the program.
Without LSP,
public class Rectangle { private int width; private int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } public class Square extends Rectangle { @Override public void setWidth(int width) { super.setWidth(width); super.setHeight(width); } @Override public void setHeight(int height) { super.setWidth(height); super.setHeight(height); } }
With LSP,
public interface Shape { int getArea(); } public class Rectangle implements Shape { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } @Override public int getArea() { return width * height; } } public class Square implements Shape { private int side; public Square(int side) { this.side = side; } @Override public int getArea() { return side * side; } }
Here, Square and Rectangle both implement Shape without breaking substitutability.
Interface Segregation Principle (ISP):
Clients should not be forced to implement interfaces they do not use.
Without ISP,
public interface Vehicle { void startEngine(); void fly(); } public class Car implements Vehicle { @Override public void startEngine() { // Start engine } @Override public void fly() { throw new UnsupportedOperationException("Cars can't fly"); } }
With ISP,
public interface EngineVehicle { void startEngine(); } public interface FlyingVehicle { void fly(); } public class Car implements EngineVehicle { @Override public void startEngine() { // Start engine } } public class Airplane implements EngineVehicle, FlyingVehicle { @Override public void startEngine() { // Start engine } @Override public void fly() { // Fly logic } }
Dependency Inversion Principle (DIP):
High-level modules should not depend on low-level modules; both should depend on abstractions.
Without DIP,
public class UserRepository { public void save(User user) { // Save logic } } public class UserService { private final UserRepository userRepository = new UserRepository(); public void saveUser(User user) { userRepository.save(user); } }
With DIP,
public interface UserRepository { void save(User user); } public class JpaUserRepository implements UserRepository { @Override public void save(User user) { // Save logic } } public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void saveUser(User user) { userRepository.save(user); } }
By depending on UserRepository, the UserService can work with any repository implementation.
By following these SOLID principles, you ensure your Java code is modular, reusable, and easier to maintain.
Similar Post
SOLID Principles Using Kotlin
- 22 Jan, 2025
- 3 min read
Master the SOLID principles to design robust, maintainable, and scalable software. Learn to create clean code by ensuring single responsibility, extensibility, substitutability, focused interfaces, and dependency management.
SOLID Principles Using Javascript
- 22 Jan, 2025
- 3 min read
Master the SOLID principles in JavaScript to create robust, maintainable, and scalable software. Ensure clean code through single responsibility, extensibility, substitutability, focused interfaces, and effective dependency management.