When we build software, the way we organize classes and code matters a lot. Good design makes programs easier to fix, grow, and understand. The SOLID principles are five important rules that help programmers write cleaner and safer code. Let’s break them down with simple explanations and examples.
1. Single Responsibility Principle (SRP)
A class should only do one job. And it should have Only one reason to change. Don’t make one class handle too many things. Each class should focus on a single task.
Why: If a class does too much, changing one part can accidentally break another.
// Wrong: It Does two jobs
class UserService {
void createUser(User user) {
/* save to DB */
}
void sendWelcomeEmail(User user) {
/* email logic */
}
}
// Right: Split into two classes
class UserRepository {
void save(User user) { /* save to DB */ }
}
class EmailService {
void sendWelcomeEmail(User user) { /* email logic */ }
}
2. Open/Closed Principle (OCP)
Code should be easy to extend, but you shouldn’t have to change old code. In Simple words: Extending new functionality should not need to change business logic OR Add new features without Modifying tested code.
Why: Keeps your system safer and easier to grow.
// Wrong: Adding a new method means editing this class
class PaymentProcessor {
void pay(String method) {
if (method.equals("credit")) { /* credit card */ }
else if (method.equals("paypal")) { /* PayPal */ }
}
}
// Right: Add new payment types without changing old code
interface PaymentMethod { void pay(); }
class CreditCardPayment implements PaymentMethod {
public void pay() { /* credit card logic */ }
}
class PayPalPayment implements PaymentMethod {
public void pay() { /* PayPal logic */ }
}
class PaymentProcessor {
void process(PaymentMethod payment) { payment.pay(); }
}
3. Liskov Substitution Principle (LSP)
Child classes should work like their parent classes without causing problems. If something works for a parent class, it should also work for a child class.
Why: Keeps your program predictable and safe.
// Wrong: Ostrich is a bird but can’t fly
class Bird { void fly() { } }
class Ostrich extends Bird { void fly() { throw new UnsupportedOperationException(); } }
// Right: Separate flying birds from non-flying birds
interface Bird {
void wings();
}
interface FlyingBird extends Bird { void fly(); }
class Sparrow implements FlyingBird {
public void wings() { /* sparrow has wings */ }
public void fly() { /* sparrow flies */ }
}
class Ostrich implements Bird {
public void wings() { /* Ostrich has wings */ }
/* no fly method needed */
}
4. Interface Segregation Principle (ISP)
Don’t force classes to use methods they don’t need. It’s better to have small, specific interfaces instead of one big one.
Why: Saves classes from having useless code.
// Wrong: Printer doesn’t need scan or fax
interface Machine {
void print();
void scan();
void fax();
}
class Printer implements Machine {
public void print() { }
public void scan() { throw new UnsupportedOperationException(); }
public void fax() { throw new UnsupportedOperationException(); }
}
// Right: Smaller, focused interfaces
interface Printer { void print(); }
interface Scanner { void scan(); }
interface Fax { void fax(); }
class BasicPrinter implements Printer {
public void print() { }
}
class MultiFunctionPrinter implements Printer, Scanner, Fax {
public void print() { }
public void scan() { }
public void fax() { }
}
5. Dependency Inversion Principle (DIP)
Depend on ideas, not details. Classes should use interfaces or abstract classes, not fixed code.
Why: Makes systems easier to change and test.
// Wrong: Always stuck with MySQL
class UserService {
private MySQLUserRepository repo = new MySQLUserRepository();
void createUser(User user) { repo.save(user); }
}
// Right: Can switch databases easily
interface UserRepository { void save(User user); }
class MySQLUserRepository implements UserRepository {
public void save(User user) { /* MySQL logic */ }
}
class MongoUserRepository implements UserRepository {
public void save(User user) { /* Mongo logic */ }
}
class UserService {
private UserRepository repo;
public UserService(UserRepository repo) { this.repo = repo; }
/* Now its depend on Abstraction instead lower level module */
void createUser(User user) { repo.save(user); }
}
Summary
SRP => One class, one job.
OCP => Add new features without changing old code.
LSP => Subclasses should act like their parent classes.
ISP => Use small, simple interfaces.
DIP => Depend on ideas, not details.
Conclusion
Following the SOLID principles helps make software easier to change, test, and keep working as it grows. Even though the names sound big, the ideas are simple: write code that is clear, focused, and easy to build on later.
Top comments (0)