SOLID Principles

Single Responsibility Principle (SRP)
A class should have one reason to change — one job. If a class does two things, a change in one concern forces you to touch the other.

Bad:

class Report {
void generateReport() { /* build report data */ }
void saveToFile(String path) { /* write to disk */ }
}

Good: split responsibilities.

class Report {
String build() { return "report"; }
}
class ReportSaver {
void saveToFile(Report r, String path) { /* write r.build() to disk */ }
}

Now formatting/generation and persistence change independently.


Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification. You should be able to add new behavior without changing existing tested code.

Bad:

class DiscountCalculator {
double apply(double price, String customerType) {
if ("VIP".equals(customerType)) return price * 0.8;
if ("EMPLOYEE".equals(customerType)) return price * 0.5;
return price;
}
}

Good: use polymorphism (extend, don’t edit).

interface Discount {
double apply(double price);
}
class VipDiscount implements Discount { public double apply(double p){return p*0.8;} }
class EmployeeDiscount implements Discount { public double apply(double p){return p*0.5;} }
class DiscountCalculator {
double apply(double price, Discount discount) { return discount.apply(price); }
}

Add a new discount by implementing Discount — no changes to DiscountCalculator.


Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without surprising behavior. A derived class shouldn’t break expectations of code using the base type.

Bad:

class Rectangle {
void setWidth(int w) { /*...*/ }
void setHeight(int h) { /*...*/ }
}
class Square extends Rectangle {
@Override
void setWidth(int w) { super.setWidth(w); super.setHeight(w); }
@Override
void setHeight(int h){ super.setWidth(h); super.setHeight(h); }
}

This breaks client code that expects independent width/height behavior.

Good: model differently.

interface Shape { int area(); }
class Rectangle implements Shape {
int width, height;
Rectangle(int w,int h){width=w;height=h;}
public int area(){ return width*height; }
}
class Square implements Shape {
int side;
Square(int s){ side=s; }
public int area(){ return side*side; }
}

Clients use Shape and don’t assume setters that break invariants.


Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they don’t use. Prefer many small, specific interfaces over one fat interface.

Bad:

interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { /* ok */ }
public void eat() { throw new UnsupportedOperationException(); }
}

Robot is forced to implement eat() it doesn’t need.

Good: split interfaces.

interface Workable { void work(); }
interface Eatable { void eat(); }
class Human implements Workable, Eatable {
public void work(){ }
public void eat(){ }
}
class Robot implements Workable {
public void work(){ }
}

Each class implements only what it needs.


Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details depend on abstractions.

Bad:

class MySQLDatabase {
void save(String data){ /* ... */ }
}
class UserService {
private MySQLDatabase db = new MySQLDatabase();
void saveUser(String u){ db.save(u); }
}

UserService is tightly coupled to MySQLDatabase.

Good: depend on an interface.

interface Database { void save(String data); }
class MySQLDatabase implements Database { public void save(String d){ /*...*/ } }
class UserService {
private final Database db;
UserService(Database db){ this.db = db; }
void saveUser(String u){ db.save(u); }
}

Now you can inject MySQLDatabase, InMemoryDatabase, or a mock for tests.


Leave a comment