π SOLID Principles in Java
The SOLID principles help write clean, organized, and maintainable code. These "rules" prevent confusion and bugs, enabling scalable development.
π₯ 1. Single Responsibility Principle (SRP)
Rule: A class should have only one responsibility.
π‘ Example
A Student class should manage only student details, not handle saving data.
// β BAD
class Student {
void getDetails() { /* Fetch student details */ }
void saveToFile() { /* Save to file */ } // Unrelated work
}
// β
GOOD
class Student {
void getDetails() { /* Fetch student details */ }
}
class FileManager {
void saveToFile() { /* Save to file */ }
}
π₯ 2. Open/Closed Principle (OCP)
Rule: Classes should be open for extension but closed for modification.
π‘ Example
Adding new shapes without changing existing code:
// β BAD
class AreaCalculator {
double calculate(String shape, double radius, double length) {
if (shape.equals("Circle")) return Math.PI * radius * radius;
else if (shape.equals("Square")) return length * length;
// Keep adding conditions for new shapes
}
}
// β
GOOD
abstract class Shape {
abstract double calculateArea();
}
class Circle extends Shape {
double radius;
Circle(double radius) { this.radius = radius; }
double calculateArea() { return Math.PI * radius * radius; }
}
class Square extends Shape {
double length;
Square(double length) { this.length = length; }
double calculateArea() { return length * length; }
}
π₯ 3. Liskov Substitution Principle (LSP)
Rule: Subclasses should work as substitutes for their parent class without breaking behavior.
π‘ Example
If Bird is the parent class, subclasses like Sparrow should behave like birds.
// β BAD
class Bird {
void fly() { System.out.println("Flying"); }
}
class Penguin extends Bird {
// Penguins don't fly! Violates LSP
}
// β
GOOD
abstract class Bird {
abstract void move();
}
class Sparrow extends Bird {
void move() { System.out.println("Flying"); }
}
class Penguin extends Bird {
void move() { System.out.println("Swimming"); }
}
π₯ 4. Interface Segregation Principle (ISP)
Rule: Donβt force a class to implement methods it doesn't need.
π‘ Example
// β BAD
interface Animal {
void fly();
void swim();
}
class Dog implements Animal {
public void fly() { /* Empty or throws error */ } // Dogs don't fly
public void swim() { System.out.println("Dog swimming"); }
}
// β
GOOD
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Dog implements Swimmable {
public void swim() { System.out.println("Dog swimming"); }
}
π₯ 5. Dependency Inversion Principle (DIP)
Rule: High-level modules should depend on abstractions, not low-level details.
π‘ Example
// β BAD
class Keyboard { /* Low-level module */ }
class Computer {
Keyboard keyboard = new Keyboard(); // Hardcoded dependency
}
// β
GOOD
interface InputDevice { /* Abstract */ }
class Keyboard implements InputDevice { /* Low-level */ }
class Computer {
InputDevice device; // Uses abstraction
Computer(InputDevice device) { this.device = device; }
}
π― Why Use These Principles?
- π οΈ Cleaner code that's easier to understand.
- π Reduces bugs and unexpected behavior.
- π Simplifies adding features without breaking existing code.
π Library Management System (LLD Example in Java)
This system demonstrates the SOLID principles in action to design a scalable and maintainable Library Management System.
π― Scenario
The Library Management System allows users to:
- π Search for books by title or author.
- π Borrow and return books.
- πΎ Maintain user and book data.
π‘ Code Implementation
πΈ 1. Book Class
The Book class represents a book in the library. It maintains its data (e.g., title, author, availability) and provides methods to borrow or return the book.
class Book {
private String id;
private String title;
private String author;
private boolean isAvailable;
public Book(String id, String title, String author) {
this.id = id;
this.title = title;
this.author = author;
this.isAvailable = true;
}
public String getId() { return id; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public boolean isAvailable() { return isAvailable; }
public void borrowBook() { this.isAvailable = false; }
public void returnBook() { this.isAvailable = true; }
}
πΈ 2. User Class
The User class represents a library user with basic details like userId and name.
class User {
private String userId;
private String name;
public User(String userId, String name) {
this.userId = userId;
this.name = name;
}
public String getUserId() { return userId; }
public String getName() { return name; }
}
πΈ 3. Library Class
The Library class manages the collection of books and provides functionality for searching, borrowing, and returning books.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Library {
private List<Book> books;
public Library() {
books = new ArrayList<>();
}
public void addBook(Book book) {
books.add(book);
}
public List<Book> searchByTitle(String title) {
return books.stream()
.filter(book -> book.getTitle().equalsIgnoreCase(title))
.collect(Collectors.toList());
}
public List<Book> searchByAuthor(String author) {
return books.stream()
.filter(book -> book.getAuthor().equalsIgnoreCase(author))
.collect(Collectors.toList());
}
public boolean borrowBook(String bookId, User user) {
for (Book book : books) {
if (book.getId().equals(bookId) && book.isAvailable()) {
book.borrowBook();
System.out.println(user.getName() + " borrowed " + book.getTitle());
return true;
}
}
System.out.println("Book not available for borrowing.");
return false;
}
public void returnBook(String bookId, User user) {
for (Book book : books) {
if (book.getId().equals(bookId) && !book.isAvailable()) {
book.returnBook();
System.out.println(user.getName() + " returned " + book.getTitle());
return;
}
}
System.out.println("Invalid return attempt.");
}
}
πΈ 4. Main Class
The LibraryManagementSystem class tests the functionality of the library system.
public class LibraryManagementSystem {
public static void main(String[] args) {
// Create Library
Library library = new Library();
// Add Books
library.addBook(new Book("1", "Java Basics", "John Doe"));
library.addBook(new Book("2", "Python Guide", "Jane Smith"));
library.addBook(new Book("3", "Data Structures", "John Doe"));
// Create User
User user = new User("101", "Alice");
// Search by Title
System.out.println("Search Results for 'Java Basics':");
for (Book book : library.searchByTitle("Java Basics")) {
System.out.println(book.getTitle() + " by " + book.getAuthor());
}
// Borrow Book
library.borrowBook("1", user);
// Try to Borrow the Same Book Again
library.borrowBook("1", user);
// Return Book
library.returnBook("1", user);
// Try to Return the Same Book Again
library.returnBook("1", user);
}
}
π οΈ Output
Search Results for 'Java Basics':
Java Basics by John Doe
Alice borrowed Java Basics
Book not available for borrowing.
Alice returned Java Basics
Invalid return attempt.
π SOLID Principles Applied
- SRP: Each class handles a single responsibility:
Bookfor book details,Userfor user information, andLibraryfor operations. - OCP: Easily extendable (e.g., adding genres or additional features).
- LSP: Subclasses (if introduced) will follow their parent class without breaking behavior.
- ISP: Interfaces can be introduced for specific types of books (e.g., EBooks, AudioBooks).
- DIP:
Libraryinteracts withBookandUservia abstraction methods, avoiding hard dependencies.
π Parking Lot System (LLD Example in Java)
This system demonstrates a scalable, thread-safe, and maintainable Parking Lot design based on SOLID principles.
π― Requirements
- π’ Multiple levels, each with a set number of spots.
- π Support for different vehicle types: cars, motorcycles, trucks.
- π Assign and release parking spots dynamically.
- π Track real-time availability of parking spots.
- π ΏοΈ Handle multiple entry and exit points with concurrency.
π‘ Code Implementation
πΈ 1. Enumerations
enum VehicleType {
CAR, MOTORCYCLE, TRUCK
}
πΈ 2. Vehicle Class Hierarchy
Abstract Class: Vehicle
abstract class Vehicle {
private String licensePlate;
private VehicleType type;
public Vehicle(String licensePlate, VehicleType type) {
this.licensePlate = licensePlate;
this.type = type;
}
public String getLicensePlate() { return licensePlate; }
public VehicleType getType() { return type; }
}
Subclasses: Car, Motorcycle, Truck
class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, VehicleType.CAR);
}
}
class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) {
super(licensePlate, VehicleType.MOTORCYCLE);
}
}
class Truck extends Vehicle {
public Truck(String licensePlate) {
super(licensePlate, VehicleType.TRUCK);
}
}
πΈ 3. ParkingSpot Class
Represents a parking spot and ensures thread safety.
class ParkingSpot {
private VehicleType spotType;
private boolean isAvailable;
private Vehicle parkedVehicle;
public ParkingSpot(VehicleType spotType) {
this.spotType = spotType;
this.isAvailable = true;
}
public synchronized boolean parkVehicle(Vehicle vehicle) {
if (isAvailable && vehicle.getType() == spotType) {
this.parkedVehicle = vehicle;
this.isAvailable = false;
return true;
}
return false;
}
public synchronized void removeVehicle() {
this.parkedVehicle = null;
this.isAvailable = true;
}
public boolean isAvailable() { return isAvailable; }
public Vehicle getParkedVehicle() { return parkedVehicle; }
}
πΈ 4. Level Class
Handles parking and releasing vehicles at a specific level.
import java.util.ArrayList;
import java.util.List;
class Level {
private int levelNumber;
private List<ParkingSpot> spots;
public Level(int levelNumber, int numSpots, VehicleType spotType) {
this.levelNumber = levelNumber;
this.spots = new ArrayList<>();
for (int i = 0; i < numSpots; i++) {
spots.add(new ParkingSpot(spotType));
}
}
public boolean parkVehicle(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.parkVehicle(vehicle)) {
System.out.println("Vehicle parked at level " + levelNumber);
return true;
}
}
return false;
}
public void releaseVehicle(String licensePlate) {
for (ParkingSpot spot : spots) {
if (spot.getParkedVehicle() != null &&
spot.getParkedVehicle().getLicensePlate().equals(licensePlate)) {
spot.removeVehicle();
System.out.println("Vehicle removed from level " + levelNumber);
return;
}
}
System.out.println("Vehicle not found in level " + levelNumber);
}
public int getAvailableSpots() {
return (int) spots.stream().filter(ParkingSpot::isAvailable).count();
}
}
πΈ 5. ParkingLot Class (Singleton)
Represents the entire parking lot and ensures only one instance exists.
import java.util.ArrayList;
import java.util.List;
class ParkingLot {
private static ParkingLot instance;
private List<Level> levels;
private ParkingLot() {
levels = new ArrayList<>();
}
public static synchronized ParkingLot getInstance() {
if (instance == null) {
instance = new ParkingLot();
}
return instance;
}
public void addLevel(Level level) {
levels.add(level);
}
public boolean parkVehicle(Vehicle vehicle) {
for (Level level : levels) {
if (level.parkVehicle(vehicle)) {
return true;
}
}
System.out.println("No parking spot available for vehicle: " + vehicle.getLicensePlate());
return false;
}
public void releaseVehicle(String licensePlate) {
for (Level level : levels) {
level.releaseVehicle(licensePlate);
}
}
public void displayAvailableSpots() {
for (int i = 0; i < levels.size(); i++) {
System.out.println("Level " + i + ": " + levels.get(i).getAvailableSpots() + " spots available.");
}
}
}
πΈ 6. Main Class
Demonstrates the usage of the parking lot system.
public class Main {
public static void main(String[] args) {
// Initialize Parking Lot
ParkingLot parkingLot = ParkingLot.getInstance();
parkingLot.addLevel(new Level(0, 2, VehicleType.CAR));
parkingLot.addLevel(new Level(1, 3, VehicleType.MOTORCYCLE));
parkingLot.addLevel(new Level(2, 1, VehicleType.TRUCK));
// Create Vehicles
Vehicle car1 = new Car("CAR123");
Vehicle bike1 = new Motorcycle("BIKE123");
Vehicle truck1 = new Truck("TRUCK123");
// Park Vehicles
parkingLot.parkVehicle(car1);
parkingLot.parkVehicle(bike1);
parkingLot.parkVehicle(truck1);
// Display Available Spots
parkingLot.displayAvailableSpots();
// Release Vehicles
parkingLot.releaseVehicle("CAR123");
parkingLot.releaseVehicle("BIKE123");
// Display Available Spots Again
parkingLot.displayAvailableSpots();
}
}
π οΈ Output
Vehicle parked at level 0
Vehicle parked at level 1
Vehicle parked at level 2
Level 0: 1 spots available.
Level 1: 2 spots available.
Level 2: 0 spots available.
Vehicle removed from level 0
Vehicle removed from level 1
Level 0: 2 spots available.
Level 1: 3 spots available.
Level 2: 0 spots available.
π Key Features
- Thread-Safety: Ensured using
synchronizedkeyword for critical sections. - Singleton Pattern: Maintains a single
ParkingLotinstance. - Modular Design: Each class handles one responsibility (SRP).
- Scalability: Easily add new levels or support additional vehicle types.



