SOLID Principles in Java with Examples ?

The SOLID principles in Java (and object-oriented programming in general) are five design principles that help developers create scalable, maintainable, and robust code. Each letter in SOLID represents one of the principles:

  1. S – Single Responsibility Principle (SRP)
  2. O – Open-Closed Principle (OCP)
  3. L – Liskov Substitution Principle (LSP)
  4. I – Interface Segregation Principle (ISP)
  5. D – Dependency Inversion Principle (DIP)

These principles were introduced by Robert C. Martin, also known as “Uncle Bob.” Let’s explore each one in detail.


1. Single Responsibility Principle (SRP)

  • Definition: A class should have only one reason to change, meaning it should have only one job or responsibility.
  • Purpose: This principle ensures that each class has a single role or responsibility, making the code easier to maintain, test, and understand.
  • Example: A User class should handle user-specific information and actions, while a separate UserRepository class should handle database interactions related to users.
   public class User { // Single Responsibility: Handle User Data
       private String name;
       private String email;
       // Other user data and methods
   }

   public class UserRepository { // Single Responsibility: Handle Database
       public void save(User user) {
           // Code to save user in the database
       }
   }

2. Open-Closed Principle (OCP)

  • Definition: Software entities (classes, modules, functions) should be open for extension but closed for modification.
  • Purpose: Allows for behavior to be extended without altering existing code, which helps prevent introducing new bugs and allows adding new features easily.
  • Example: Instead of modifying an existing Payment class to add more payment types, we can use inheritance to create subclasses for different payment types (e.g., CreditCardPayment, PaypalPayment).
   public interface Payment {
       void pay(double amount);
   }

   public class CreditCardPayment implements Payment {
       public void pay(double amount) {
           // Credit card payment logic
       }
   }

   public class PaypalPayment implements Payment {
       public void pay(double amount) {
           // Paypal payment logic
       }
   }

3. Liskov Substitution Principle (LSP)

  • Definition: Objects of a superclass should be replaceable with objects of a subclass without affecting the functionality of the program.
  • Purpose: This principle ensures that derived classes extend the functionality of the base class without changing its behavior, maintaining polymorphism.
  • Example: If a Bird class has a fly() method, then all subclasses like Sparrow and Eagle should logically be able to fly. A subclass like Penguin, which cannot fly, would violate this principle.
   public class Bird {
       public void fly() {
           System.out.println("Flying");
       }
   }

   public class Sparrow extends Bird {
       // Can fly, so it obeys LSP
   }

   public class Penguin extends Bird {
       // Violates LSP if it tries to inherit "fly"
       @Override
       public void fly() {
           throw new UnsupportedOperationException("Penguins can't fly");
       }
   }

4. Interface Segregation Principle (ISP)

  • Definition: A client should not be forced to implement interfaces it does not use. Instead of one large interface, create smaller, more specific interfaces.
  • Purpose: This principle keeps interfaces lean and focused, ensuring that classes are not burdened with irrelevant methods.
  • Example: Rather than one Animal interface with multiple unrelated methods, it is better to have specific interfaces like Flyable, Swimmable, or Runnable.
   public interface Flyable {
       void fly();
   }

   public interface Swimmable {
       void swim();
   }

   public class Duck implements Flyable, Swimmable {
       public void fly() {
           // Duck flying logic
       }

       public void swim() {
           // Duck swimming logic
       }
   }

5. Dependency Inversion Principle (DIP)

  • Definition: High-level modules should not depend on low-level modules; both should depend on abstractions. Also, abstractions should not depend on details; details should depend on abstractions.
  • Purpose: This principle helps in reducing tight coupling and increases the flexibility and reusability of code by relying on interfaces or abstract classes.
  • Example: A NotificationService class should not depend on specific classes like EmailService or SMSService. Instead, it should depend on an INotification interface that EmailService and SMSService implement.
   public interface Notification {
       void send(String message);
   }

   public class EmailService implements Notification {
       public void send(String message) {
           // Email sending logic
       }
   }

   public class SMSService implements Notification {
       public void send(String message) {
           // SMS sending logic
       }
   }

   public class NotificationService {
       private Notification notification;

       public NotificationService(Notification notification) {
           this.notification = notification;
       }

       public void notifyUser(String message) {
           notification.send(message);
       }
   }
  • Here, NotificationService depends on the Notification interface, making it easy to swap implementations.

Summary of SOLID Principles

The SOLID principles provide a foundation for designing clean, maintainable, and extensible code in object-oriented programming:

  • S: Single Responsibility Principle (SRP) – A class should have only one reason to change.
  • O: Open-Closed Principle (OCP) – Open for extension, closed for modification.
  • L: Liskov Substitution Principle (LSP) – Subtypes must be replaceable for their base types.
  • I: Interface Segregation Principle (ISP) – No client should be forced to depend on methods it does not use.
  • D: Dependency Inversion Principle (DIP) – Depend on abstractions, not on concrete implementations.

By adhering to these principles, developers can write code that is more modular, testable, and easier to understand.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *