Understanding the SOLID Principles with Real-Life Examples

Introduction

The SOLID principles are five design principles intended to make software designs more understandable, flexible, and maintainable. These principles help software engineers write clean, scalable, and robust code.


1. Single Responsibility Principle (SRP)

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

Real-Life Spring Boot Example:

Suppose you're building a Church Management System.

Wrong - Violates SRP:

@Service
public class MemberService {
    public void registerMember(Member member) {
        // Save to DB
    }

    public void sendWelcomeEmail(Member member) {
        // Logic to send email (not MemberService's job!)
    }
}

Correct - Follows SRP:

@Service
public class MemberService {
    public void registerMember(Member member) {
        // Save to DB
    }
}

@Service
public class EmailService {
    public void sendWelcomeEmail(Member member) {
        // Send email
    }
}

Now MemberService handles only registration, while EmailService is responsible for emails.


2. Open/Closed Principle (OCP)

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

Real-Life Spring Boot Example:

You're handling different types of notifications (Email, SMS, Push).

Wrong - Violates OCP:

public class NotificationService {
    public void send(String type, String message) {
        if (type.equals("EMAIL")) {
            // send email
        } else if (type.equals("SMS")) {
            // send SMS
        }
        // Add more 'if' conditions as types grow — BAD!
    }
}

Correct - Follows OCP using interfaces:

public interface Notifier {
    void send(String message);
}

@Service
public class EmailNotifier implements Notifier {
    public void send(String message) {
        // Email logic
    }
}

@Service
public class SmsNotifier implements Notifier {
    public void send(String message) {
        // SMS logic
    }
}

@Service
public class NotificationService {
    private final List<Notifier> notifiers;

    public NotificationService(List<Notifier> notifiers) {
        this.notifiers = notifiers;
    }

    public void notifyAll(String message) {
        notifiers.forEach(n -> n.send(message));
    }
}

You can now add new notification types without changing existing code.


3. Liskov Substitution Principle (LSP)

Definition: Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.

Real-Life Spring Boot Example:

Imagine you have a User entity and different roles: Admin, Member.

Wrong - Violates LSP:

public class MemberUser extends User {
    @Override
    public void deleteUser(User user) {
        throw new UnsupportedOperationException("Members can't delete");
    }
}

This breaks LSP — if a MemberUser is passed in place of User, it will crash.

Correct - Follows LSP:
Design your system so each subclass behavior is valid:

public abstract class User {
    public abstract void accessDashboard();
}

public class AdminUser extends User {
    public void accessDashboard() {
        // full access
    }
}

public class MemberUser extends User {
    public void accessDashboard() {
        // limited access
    }
}

4. Interface Segregation Principle (ISP)

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

Real-Life Spring Boot Example:

Wrong - One Fat Interface:

public interface UserService {
    void register();
    void login();
    void deleteUser();
    void approveAdmin();  // Only for Admin
}

Now even normal users must implement approveAdmin() — not ideal.

Correct - Split Interfaces:

public interface BasicUserService {
    void register();
    void login();
}

public interface AdminService extends BasicUserService {
    void deleteUser();
    void approveAdmin();
}

Now, services implement only what they need.


5. Dependency Inversion Principle (DIP)

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

Real-Life Spring Boot Example:

Wrong - Tight coupling:

public class ReportService {
    private final PdfReportGenerator generator = new PdfReportGenerator();
}

Correct - Use abstraction (interface + dependency injection):

public interface ReportGenerator {
    void generate();
}

@Service
public class PdfReportGenerator implements ReportGenerator {
    public void generate() {
        // generate PDF
    }
}

@Service
public class ReportService {
    private final ReportGenerator generator;

    public ReportService(ReportGenerator generator) {
        this.generator = generator;
    }
}

This allows switching to ExcelReportGenerator without changing ReportService.


Summary Table

PrincipleGoalSpring Boot Example
SRPOne class = one responsibilitySeparate EmailService from MemberService
OCPExtend, don’t modifyUse Notifier interface for notifications
LSPSubtypes can replace base typesSubclass User without throwing exceptions
ISPKeep interfaces small and focusedSplit user/admin service interfaces
DIPDepend on abstractionsUse interfaces + @Autowired for flexibility

Need for SOLID Principles in Object-Oriented Design

Below are some of the main reasons why solid principles are important in object oriented design:

  • SOLID principles make code easier to maintain. When each class has a clear responsibility, it's simpler to find where to make changes without affecting unrelated parts of the code.

  • These principles support growth in software. For example, the Open/Closed Principle allows developers to add new features without changing existing code, making it easier to adapt to new requirements.

  • SOLID encourages flexibility. By depending on abstractions rather than specific implementations (as in the Dependency Inversion Principle), developers can change components without disrupting the entire system

Conclusion
This guide uses real-world Spring Boot examples to help solidify your understanding of SOLID principles in backend development. These principles not only improve your code quality but also make collaboration and scaling easier in enterprise-level projects.

0
Subscribe to my newsletter

Read articles from collince kiprotich directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

collince kiprotich
collince kiprotich