Navigation

Friday 1 November 2024

The Single Responsibility Principle (SRP) of SOLID in C#

 

What is the Single Responsibility Principle (SRP)?

  • Definition of SRP: “A class should have only one reason to change.”
  • Purpose of SRP: Explain how SRP aims to limit each class to a single responsibility to avoid coupling multiple roles into a single class.
  • Benefits of SRP: Mention maintainability, flexibility, scalability, and testability.

Example Scenario for SRP in a Console Application

  • Context: Describe a scenario where SRP might be applied, such as a simple console application to manage employee data.

  • Initial Code Without SRP: Show an example of a single class handling multiple responsibilities, like creating an employee, logging data, and sending a notification.


namespace ConsoleAppForPattern
{
    internal class EmpManager
    {
        public void AddEmployee(string name)
        {
            Console.WriteLine($"Employee {name} added.");
            LogEmployeeAddition(name);
            SendWelcomeEmail(name);
        }

        private void LogEmployeeAddition(string name)
        {
            Console.WriteLine($"Log: Employee {name} was added to the system.");
        }

        private void SendWelcomeEmail(string name)
        {
            Console.WriteLine($"Email: Welcome {name} to the company!");
        }
    }
}

  • Issues with this Code: Explain how the EmployeeManager class violates SRP by handling employee creation, logging, and email notifications in one place. Each of these tasks could change for different reasons, so they should be separated.



Refactoring to Follow SRP

  • Identify Separate Responsibilities: Break down the single EmployeeManager class into three separate classes, each with its own responsibility: EmployeeService, LoggerService, and EmailService.

  • New Class Structure:

    • EmployeeService: Handles employee creation.
    • LoggerService: Manages logging-related functions.
    • EmailService: Takes care of email notifications.

namespace ConsoleAppForPattern
{
    internal class EmpService
    {

        private readonly LoggerService _logger;
        private readonly EmailService _email;

        public EmpService(LoggerService logger, EmailService email)
        {
            _logger = logger;
            _email = email;
        }

        public void AddEmployee(string name)
        {
            Console.WriteLine($"Employee {name} added.");
            _logger.LogEmployeeAddition(name);
            _email.SendWelcomeEmail(name);
        }
    }

    public class LoggerService
    {
        public void LogEmployeeAddition(string name)
        {
            Console.WriteLine($"Log: Employee {name} was added to the system.");
        }
    }

    public class EmailService
    {
        public void SendWelcomeEmail(string name)
        {
            Console.WriteLine($"Email: Welcome {name} to the company!");
        }
    }
}



Explanation of the Refactored Code:
  • Describe how each class now has a single responsibility, making it easy to modify any one feature without affecting others.
  • For instance, if logging requirements change, you only need to update LoggerService.

// Main class with entry point
using ConsoleAppForPattern;

class Program
{
    static void Main(string[] args)
    {
        // Instantiate the logger and email services
        LoggerService loggerService = new LoggerService();
        EmailService emailService = new EmailService();

        // Instantiate the employee service, passing in the logger and email services
        EmpService empService = new EmpService(loggerService, emailService);

        // Add a new employee
        empService.AddEmployee("vijay");
    }
 
}



Explanation of the Main Method:

  1. Instantiate Dependencies: Create instances of LoggerService and EmailService.
  2. Inject Dependencies: Pass the LoggerService and EmailService instances to EmployeeService during its instantiation.
  3. Add an Employee: Call AddEmployee("John Doe") to simulate adding a new employee, logging the addition, and sending a welcome email.

Benefits of Applying SRP in This Example

  • Modular Design: Changes to one class don’t affect others, making the code easier to understand and modify.
  • Ease of Testing: Each class can be independently tested.
  • Enhanced Reusability: The LoggerService and EmailService classes can now be reused across different parts of the application.
  • Code Clarity: The purpose of each class is clear, simplifying onboarding for new developers.

Real-World Scenarios Where SRP is Essential

  • Enterprise Applications: In complex applications, SRP helps manage large codebases by separating concerns and minimizing dependencies.
  • Microservices Architecture: SRP is especially useful in microservices, where each service is often limited to a single responsibility.
  • Legacy Code Refactoring: SRP can make large, monolithic codebases more maintainable by breaking down classes and responsibilities.

Conclusion

  • Recap the SRP Definition and Benefits: Reinforce the core idea of SRP and its advantages.
  • Encouragement to Apply SRP: Highlight the importance of SRP in building maintainable, scalable applications and encourage readers to start applying it to their own C# projects.

No comments:

Post a Comment