The Single Responsibility Principle is the idea that a class should have only one reason to change.
We begin our dive into SOLID with the S: the Single Responsibility Principle (SRP).
This principle is closely related to the concept of cohesion we saw earlier.
The classic definition, formulated by Robert C. Martin, states:
“A class should have one, and only one, reason to change.”
Often, people confuse this with “a class should do only one thing.” While both ideas are related, they are not exactly the same. SRP talks about who causes changes in the software and grouping things that change for the same reasons.
What does “reason to change” mean?
Suppose we have a class called EmployeeReport. This class does two things:
- Calculates the employee’s salary.
- Prints the report in PDF format.
How many reasons does this class have to change?
- If the Finance department changes the payroll calculation rules… the class changes.
- If the IT department decides to change the report format or the PDF library… the class changes.
We have two different actors (Finance and IT) that can request changes to the same class. We are violating the SRP. If we modify the class to satisfy Finance, we risk accidentally breaking the PDF generation.
SRP tells us that we should separate code that changes for different reasons or responds to different business actors.
Example of an SRP violation
Let’s see a very common example: the typical User class that starts small and ends up becoming a “god object.”
// ❌ BAD DESIGN: SRP violation
public class User
{
public string Name { get; set; }
public string Email { get; set; }
// Reason for change 1: Business logic (Validation)
public bool ValidateEmail()
{
return Email.Contains("@");
}
// Reason for change 2: Persistence (Database)
public void SaveToDatabase()
{
// SQL code to insert user...
Console.WriteLine($"Saving {Name} in SQL...");
}
// Reason for change 3: Communication (Infrastructure)
public void SendWelcomeEmail()
{
// SMTP configuration and sending...
Console.WriteLine($"Sending email to {Email}...");
}
}This class knows too much. It knows business rules, how to talk to the database, and how to send emails. It has low cohesion.
If we change the database from SQL Server to MongoDB, we have to modify the User class. Does that make sense? No. The user is a business concept; it shouldn’t care where it’s stored.
Refactoring applying SRP
To comply with the single responsibility principle, we must separate these responsibilities into distinct classes.
The User class should be just that: a representation of the user’s data (a domain POCO, for example).
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}We create a class exclusively responsible for saving and retrieving data.
public class UserRepository
{
public void Save(User user)
{
Console.WriteLine($"Saving {user.Name} in SQL...");
}
}We create a class exclusively responsible for communications.
public class EmailService
{
public void SendWelcome(User user)
{
Console.WriteLine($"Sending email to {user.Email}...");
}
}Finally, we can have a service that coordinates everything. Notice that now each piece does its own job.
// ✅ GOOD DESIGN: High Cohesion
public class RegistrationService
{
private UserRepository _repo;
private EmailService _emailService;
// Dependency Injection (topic for the DIP principle)
public RegistrationService(UserRepository repo, EmailService emailService)
{
_repo = repo;
_emailService = emailService;
}
public void RegisterUser(User user)
{
if (!user.Email.Contains("@")) // Simple validation
throw new Exception("Invalid email");
_repo.Save(user);
_emailService.SendWelcome(user);
}
}Benefits of applying SRP
At first, it seems like we’ve done more work: we went from one class to four. Still, the separation is worth it.
- Maintainability: If there’s an error in sending emails, we know exactly where to look (
EmailService). We don’t have to dive into a 2000-line class. - Reusability: We can use the
EmailServiceto send invoice emails or alerts; it’s not tied to theUserclass. - Testability: Testing the
Userclass is trivial. Testing theUserRepositorycan be done separately without accidentally sending real emails. - Collaboration: One developer can be improving the database while another improves the email templates, and they won’t touch the same file.
Where is the limit?
Here’s the catch. If we take SRP to the extreme, we could end up with classes that have a single method (SaveUser, ReadUser, DeleteUser…). This is known as excessive fragmentation and is also bad.
The criterion is to group functions that change together. If every time you change the validation you also usually change the save format, perhaps they should be together (though it’s rare). Use common sense.
With small, focused classes, the next problem is changing their behavior without modifying their code.
Next step in the course: