SOLID Principles – Do you understand them?
September 25, 2024
The SOLID principles help in writing clean, scalable, and maintainable code, and they are highly applicable in Java as well. Let’s go through each principle with examples in Java.
1. Single Responsibility Principle (SRP)
A class should have only one reason to change, meaning it should have only one responsibility.
Example:
javaCopy code// BAD: Violates SRP because the User class handles both user data and database operations
class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public void saveToDatabase() {
// Code to save user data to the database
System.out.println("User saved to database");
}
}
// GOOD: Each class has a single responsibility
class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Getters and Setters
}
class UserRepository {
public void saveToDatabase(User user) {
// Code to save user to the database
System.out.println("User " + user.getUsername() + " saved to the database");
}
}
Here, User
handles user data, and UserRepository
handles database operations.
2. Open/Closed Principle (OCP)
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Example:
javaCopy code// BAD: Violates OCP because modifying this class requires changing existing code
class AreaCalculator {
public double calculateRectangleArea(Rectangle rectangle) {
return rectangle.getHeight() * rectangle.getWidth();
}
public double calculateCircleArea(Circle circle) {
return Math.PI * circle.getRadius() * circle.getRadius();
}
}
// GOOD: Follows OCP by using an interface for shapes
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
private double height;
private double width;
public Rectangle(double height, double width) {
this.height = height;
this.width = width;
}
public double calculateArea() {
return height * width;
}
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.calculateArea();
}
}
Here, you can extend the functionality by adding new shapes without modifying the existing AreaCalculator
class.
3. Liskov Substitution Principle (LSP)
Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.
Example:
javaCopy code// BAD: Violates LSP because Square's setWidth and setHeight methods don't behave correctly
class Rectangle {
protected double height;
protected double width;
public void setHeight(double height) {
this.height = height;
}
public void setWidth(double width) {
this.width = width;
}
public double calculateArea() {
return height * width;
}
}
class Square extends Rectangle {
@Override
public void setHeight(double height) {
this.height = height;
this.width = height; // A square must have equal width and height
}
@Override
public void setWidth(double width) {
this.width = width;
this.height = width; // A square must have equal width and height
}
}
// GOOD: Avoid forcing a square into the rectangle structure
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
private double height;
private double width;
public Rectangle(double height, double width) {
this.height = height;
this.width = width;
}
public double calculateArea() {
return height * width;
}
}
class Square implements Shape {
private double side;
public Square(double side) {
this.side = side;
}
public double calculateArea() {
return side * side;
}
}
Here, Square
and Rectangle
both implement the Shape
interface correctly without violating the principle.
4. Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they do not use.
Example:
javaCopy code// BAD: A large interface that forces implementation of unnecessary methods
interface Worker {
void work();
void eat();
}
class Employee implements Worker {
public void work() {
System.out.println("Employee working");
}
public void eat() {
System.out.println("Employee eating");
}
}
class Robot implements Worker {
public void work() {
System.out.println("Robot working");
}
// Robot doesn't eat, so this method is unnecessary
public void eat() {
throw new UnsupportedOperationException("Robots don't eat");
}
}
// GOOD: Split the interfaces into smaller ones
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Employee implements Workable, Eatable {
public void work() {
System.out.println("Employee working");
}
public void eat() {
System.out.println("Employee eating");
}
}
class Robot implements Workable {
public void work() {
System.out.println("Robot working");
}
}
Here, Robot
does not need to implement the eat()
method because it only implements the Workable
interface.
5. Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Example:
// BAD: High-level class directly depends on a low-level class
class Keyboard {
// Implementation details
}
class Computer {
private Keyboard keyboard;
public Computer() {
this.keyboard = new Keyboard(); // Dependency on low-level module
}
}
// GOOD: Use interfaces to depend on abstractions
interface InputDevice {
void input();
}
class Keyboard implements InputDevice {
public void input() {
System.out.println(“Keyboard input”);
}
}
class Mouse implements InputDevice {
public void input() {
System.out.println(“Mouse input”);
}
}
class Computer {
private InputDevice inputDevice;
public Computer(InputDevice inputDevice) {
this.inputDevice = inputDevice; // Depends on abstraction
}
public void getInput() {
inputDevice.input();
}
}
Now the Computer
class depends on the InputDevice
abstraction rather than a specific implementation. This allows flexibility to switch between different input devices without changing the Computer
class.
These examples show how the SOLID principles help in designing systems that are more modular, flexible, and easier to maintain.