One Class, One Responsibility: SRP in Practice


One of the pillars of clean coding (and object-oriented programming) is the Single Responsibility Principle, often abbreviated as SRP.
Simply put: each class should have one and only one reason to change.
But what does this really mean in C#? And how do you apply it in real life?
What does "one responsibility" mean?
A responsibility is a reason for change.
If a class handles both business logic and database persistence, it might need to change for two different reasons: a change in business rules or a change in the database. This makes it fragile and hard to maintain.
❌ Class with multiple responsibilities:
public class InvoiceManager
{
public void CreateInvoice(Invoice invoice)
{
// Business logic
invoice.Total = invoice.Items.Sum(i => i.Price);
// Save to database
using var context = new AppDbContext();
context.Invoices.Add(invoice);
context.SaveChanges();
// Send email
EmailService.Send(invoice);
}
}
This class:
Calculates the total (business)
Saves to the database (persistence)
Sends email (notification)
Three responsibilities = too much confusion.
How to respect SRP
✅ Separate responsibilities into distinct classes:
public class InvoiceService
{
public void CalculateTotal(Invoice invoice)
{
invoice.Total = invoice.Items.Sum(i => i.Price);
}
}
public class InvoiceRepository
{
private readonly AppDbContext _context;
public InvoiceRepository(AppDbContext context) => _context = context;
public void Save(Invoice invoice)
{
_context.Invoices.Add(invoice);
_context.SaveChanges();
}
}
public class NotificationService
{
public void SendInvoiceEmail(Invoice invoice)
{
// send email
}
}
Then compose them:
public class InvoiceManager
{
private readonly InvoiceService _service;
private readonly InvoiceRepository _repository;
private readonly NotificationService _notifier;
public InvoiceManager(InvoiceService service, InvoiceRepository repository, NotificationService notifier)
{
_service = service;
_repository = repository;
_notifier = notifier;
}
public void CreateInvoice(Invoice invoice)
{
_service.CalculateTotal(invoice);
_repository.Save(invoice);
_notifier.SendInvoiceEmail(invoice);
}
}
Benefits of SRP
✅ Easier to test: each class has a clear purpose and can be easily isolated in tests.
✅ Better maintainability: changes in one part don’t risk breaking another.
✅ Greater reusability: you can reuse NotificationService
in other contexts.
✅ Improved readability: each class name clearly reflects its function.
Isn’t this too much code?
No. It’s organized code.
Clean coding isn’t about writing fewer lines but writing better lines.
The time you save reading, testing, and fixing these small blocks pays off a hundredfold for the extra lines.
When not to apply SRP rigidly
For temporary code, prototypes, or short scripts, splitting everything may be overkill.
But for real, modular, long-term applications: SRP is a lifesaver.
Conclusion
The Single Responsibility Principle is simple to state but powerful to apply.
Write classes that do one thing — but do it well.
In the next article, we’ll talk about refactoring and duplicate code: how to transform legacy code into something cleaner and more sustainable.
Subscribe to my newsletter
Read articles from Developer Fabio directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Developer Fabio
Developer Fabio
I'm a fullstack developer and my stack is includes .net, angular, reactjs, mondodb and mssql I currently work in a little tourism company, I'm not only a developer but I manage a team and customers. I love learning new things and I like the continuous comparison with other people on ideas.