Mastering SOLID Principles with Java -
A Practical Guide
1. Single Responsibility Principle (SRP)
Definition: A class should have only one reason to change. It should have only
one responsibility.
Example:
class Invoice {
private double amount;
public Invoice(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
class InvoicePrinter {
public void printInvoice(Invoice invoice) {
System.out.println("Invoice Amount: " + invoice.getAmount());
}
}
Why SRP?
• The Invoice class only handles invoice-related data.
• The InvoicePrinter class is responsible for printing invoices.
• If printing logic changes, we modify InvoicePrinter without affecting Invoice.
2. Open/Closed Principle (OCP)
Definition: Software entities should be open for extension but closed for
modification.
Example:
interface Payment {
void pay(double amount);
}
class CreditCardPayment implements Payment {
public void pay(double amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}
class UpiPayment implements Payment {
public void pay(double amount) {
System.out.println("Paid " + amount + " using UPI.");
}
}
Why OCP?
• New payment methods (e.g., PayPal) can be added without modifying
existing code.
• The Payment interface is open for new implementations but closed for
modification.
3. Liskov Substitution Principle (LSP)
Definition: Subtypes must be substitutable for their base types without breaking
the application.
Example (Wrong Way):
class Rectangle {
protected int width, height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
this.width = this.height = width;
}
public void setHeight(int height) {
this.width = this.height = height;
}
}
Why is this wrong?
• Squaremodifies the behavior of Rectangle.
• If an algorithm depends on Rectangle, it might break when passed a Square.
Correct Approach:
interface Shape {
int getArea();
}
class Rectangle implements Shape {
protected int width, height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
public int getArea() {
return side * side;
}
}
4. Interface Segregation Principle (ISP)
Definition: A class should not be forced to implement interfaces it does not use.
Example (Wrong Way):
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() {
System.out.println("Robot is working");
}
public void eat() {
// Robots do not eat!
}
}
Correct Approach:
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class HumanWorker implements Workable, Eatable {
public void work() {
System.out.println("Human is working");
}
public void eat() {
System.out.println("Human is eating");
}
}
class Robot implements Workable {
public void work() {
System.out.println("Robot is working");
}
}
Why ISP?
• Robotdoes not need an eat() method, so we split interfaces.
• Classes only implement what they need.
5. Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules. Both
should depend on abstractions.
Example (Wrong Way):
class MySQLDatabase {
void connect() {
System.out.println("Connected to MySQL");
}
}
class Application {
private MySQLDatabase database = new MySQLDatabase();
public void start() {
database.connect();
}
}
Correct Approach (Using DIP):
interface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() {
System.out.println("Connected to MySQL");
}
}
class PostgreSQLDatabase implements Database {
public void connect() {
System.out.println("Connected to PostgreSQL");
}
}
class Application {
private Database database;
public Application(Database database) {
this.database = database;
}
public void start() {
database.connect();
}
}
Why DIP?
• depends on Database abstraction, not a concrete class.
Application
• We can easily switch databases without modifying Application.