SOLID Principles: Single Responsibility

Introduction

In the world of software development, where complexity reigns supreme, the Single Responsibility Principle (SRP) stands as a beacon of clarity and simplicity. It's the cornerstone of the SOLID principles, a set of guidelines that transform chaotic code into masterpieces of maintainability and resilience.

At its core, the SRP asserts that every class or module should have one, and only one, reason to change. This means every component of your code should focus on fulfilling a single, well-defined responsibility, just like specialists in a team.

Imagine code so modular, each piece only does one thing well. Enter the Single Responsibility Principle, your key to clean, maintainable software.

Why It Matters:

Imagine a Swiss Army Knife of a class, attempting to handle data storage, user interface, calculations, and email notifications all at once. It becomes a tangled knot of interconnected responsibilities, where a change in one area can trigger unintended consequences in others.

  • Reduces Coupling: Isolates changes, preventing unintended side effects.

  • Improves Maintainability: Easier to understand, test, and modify individual units.

  • Enhances Reusability: Classes become more versatile building blocks.

  • Promotes Readability: Clearer code structure and purpose.

Example: Bad Design

Consider a class called UserManager with these responsibilities:

  • Storing user data in a database

  • Validating user input

  • Sending email notifications

class UserManager {
    public void createUser(String name, String email) {
        // Validate user input
        if (!isValidEmail(email)) {
            throw new IllegalArgumentException("Invalid email format");
        }

        // Store user data in the database
        database.insertUser(name, email);

        // Send welcome email
        emailSender.sendWelcomeEmail(email);
    }

    private boolean isValidEmail(String email) {
        // Email validation logic
    }
}

Example: Refactored Design

class UserRepository { // Apply repository desgin patter
    public void saveUser(User user) {
        database.insertUser(user.getName(), user.getEmail());
    }
}

class UserValidator { // Separate class for validations
    public void validateUser(User user) {
        if (!isValidEmail(user.getEmail())) {
            throw new IllegalArgumentException("Invalid email format");
        }
    }

    private boolean isValidEmail(String email) {
        // Email validation logic
    }
}

class EmailSender { // Separate class for sending emails
    public void sendWelcomeEmail(String email) {
        // Email sending logic
    }
}

class UserService { // Separate class for user service
    private UserRepository userRepository;
    private UserValidator userValidator;
    private EmailSender emailSender;

    public void createUser(String name, String email) {
        User user = new User(name, email);
        userValidator.validateUser(user);
        userRepository.saveUser(user);
        emailSender.sendWelcomeEmail(user.getEmail());
    }
}
  1. Separated Responsibilities: The UserManager class was split into:

    • UserRepository: Handles data storage

    • UserValidator: Validates user input

    • EmailSender: Sends email notifications

    • UserService: Orchestrates user creation logic

  2. Cohesive Classes: Each class now has a single, well-defined responsibility.

  3. Reduced Coupling: Classes are less dependent on each other, making changes easier.

  4. Improved Testability: Each class can be tested independently.

  5. Enhanced Readability: Code is more organized and easier to understand.

Addition Tips

  • Reason to Change: Focus on the reason for change, not just the number of methods.

  • Cohesion: Ensure methods within a class are tightly related to its core responsibility.

  • Granularity: Balance having too many small classes with having overly large classes.

  • Refactoring: Don't be afraid to restructure code to better align with SRP.


With the Single Responsibility Principle as our guide, we're crafting laser-focused code, each class devoted to a single, distinct purpose. This clarity unlocks unparalleled maintainability and adaptability. Next, we'll delve into the the Open-Closed Principle empowering us to craft code that welcomes change with open arms, extending its functionality without altering its core structure. Prepare to witness the art of creating adaptable software that gracefully embraces new features without compromising its stability.

SOLID Principles: Open Closed Principle (hashnode.dev)