SOLID Principles


The SOLID principles are five key design guidelines in object-oriented programming that help make software more maintainable, flexible, and scalable. They were popularized by Robert C. Martin (Uncle Bob)

Here’s a breakdown: 


S – Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change, meaning it should do only one job.

Why:

  • Reduces complexity

  • Improves testability and reusability

Example (Java/Spring Boot):

Bad Practice:

public class InvoiceService { public void createInvoice() { /* create invoice */ } public void sendEmail() { /* send email */ } }

Good Practice:

public class InvoiceService { public void createInvoice() { /* create invoice */ } } public class EmailService { public void sendEmail() { /* send email */ } }



O – Open/Closed Principle (OCP)

Definition: Software entities (classes, modules, functions) should be open for extension but closed for modification.

Why:

  • Add new features without changing existing code

  • Prevents breaking existing functionality

Example: Instead of modifying an existing payment processor, create new implementations:

interface Payment { void pay(double amount); } class CreditCardPayment implements Payment { public void pay(double amount) { /* logic */ } } class UpiPayment implements Payment { public void pay(double amount) { /* logic */ } }
Now, you can add a PayPalPayment without editing old classes.



L – Liskov Substitution Principle (LSP)

Definition: Subclasses should be replaceable with their base class without breaking the application.

Why:

  • Ensures correct polymorphism usage

  • Avoids unexpected behaviors

Example:

Bad Practice:

class Bird { void fly() { /* flying */ } } class Ostrich extends Bird { @Override void fly() { throw new UnsupportedOperationException(); } // ❌ violates LSP }

Good Practice:

interface Bird {} interface FlyingBird extends Bird { void fly(); } class Sparrow implements FlyingBird { public void fly() {} } class Ostrich implements Bird {}



I – Interface Segregation Principle (ISP)

Definition: No client should be forced to depend on methods it does not use.

Why:

  • Avoids "fat" interfaces

  • Encourages specific, smaller interfaces

Example:

Bad Practice:

interface MultiFunctionPrinter { void print(); void scan(); void fax(); } // Violates ISP if some printers don't support fax

Good Practice:

interface Printer { void print(); } interface Scanner { void scan(); } interface Fax { void fax(); }



D – Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules; both should depend on abstractions.

Why:

  • Improves flexibility

  • Makes code testable

Example (Spring Boot DI):

interface MessageService { void sendMessage(String msg); } @Service class EmailService implements MessageService { public void sendMessage(String msg) { /* email logic */ } } @Service class SMSService implements MessageService { public void sendMessage(String msg) { /* sms logic */ } } @Component class NotificationController { private final MessageService messageService; @Autowired NotificationController(MessageService messageService) { this.messageService = messageService; } }
Here, the controller depends on interface, not a concrete class.



💡 In short:

  • S – One class = One job

  • O – Add new behavior without changing old code

  • L – Subtypes must behave like their base types

  • I – Keep interfaces small and specific

  • D – Depend on abstractions, not implementations