SOLID Principles in Java: From Theory to Practice
\n\nSOLID is an acronym for five fundamental principles of object-oriented programming, formulated by Robert Martin (Uncle Bob). These principles help create flexible, maintainable, and scalable code. In this article, we will break down each principle using Java examples, show how they are violated, and how to apply them correctly.
\n\nWhy are SOLID principles needed?
\nWithout SOLID, code quickly turns into "spaghetti." Have you encountered a situation where changing one class breaks three others? Or when a method does too much? SOLID solves these problems. Each principle is a rule that makes code:
\n- \n
- Resistant to change — new requirements do not break old logic. \n
- Understandable — each class is responsible for one thing. \n
- Testable — easy to write unit tests. \n
1. Single Responsibility Principle (SRP)
\nDefinition: "A class should have only one reason to change."
\nIn simple words: a class should do only one thing. If a class has more than one responsibility, it is difficult to maintain.
\n\nSRP Violation (Bad Code)
\npublic class OrderService {\n public void createOrder(Order order) {\n // Order creation logic\n saveToDatabase(order);\n sendEmailConfirmation(order);\n }\n\n private void saveToDatabase(Order order) {\n // Saving to DB\n }\n\n private void sendEmailConfirmation(Order order) {\n // Sending email\n }\n}\nProblem: the class is responsible for business logic, database operations, and sending notifications. Changing the email service would require changing OrderService.
Correct SRP Implementation
\npublic class OrderService {\n private final OrderRepository repository;\n private final EmailService emailService;\n\n public OrderService(OrderRepository repo, EmailService email) {\n this.repository = repo;\n this.emailService = email;\n }\n\n public void createOrder(Order order) {\n repository.save(order);\n emailService.sendConfirmation(order);\n }\n}\n\npublic class OrderRepository {\n public void save(Order order) { /* DB logic */ }\n}\n\npublic class EmailService {\n public void sendConfirmation(Order order) { /* email logic */ }\n}\nNow each class has one reason to change. OrderService changes only when the order creation business process changes.
2. Open/Closed Principle (OCP)
\nDefinition: "Classes should be open for extension but closed for modification."
\nThis means you should add new functionality through new code, not by modifying existing code.
\n\nOCP Violation (Bad Code)
\npublic class AreaCalculator {\n public double calculateArea(Object shape) {\n if (shape instanceof Rectangle) {\n Rectangle r = (Rectangle) shape;\n return r.width * r.height;\n } else if (shape instanceof Circle) {\n Circle c = (Circle) shape;\n return Math.PI * c.radius * c.radius;\n }\n throw new IllegalArgumentException("Unknown shape");\n }\n}\nTo add a triangle, you need to modify the calculateArea method — this violates OCP.
Correct OCP Implementation
\npublic interface Shape {\n double calculateArea();\n}\n\npublic class Rectangle implements Shape {\n private double width, height;\n public Rectangle(double w, double h) { this.width = w; this.height = h; }\n @Override\n public double calculateArea() { return width * height; }\n}\n\npublic class Circle implements Shape {\n private double radius;\n