Single Responsibility Principle (SRP)
1. What is Single Responsibility Principle?
Single Responsibility Principle (SRP) is the first principle of SOLID.
A class should have only one reason to change.
In simple English:
One class should focus on one responsibility.
It does not mean a class should contain only one method. A class can have multiple methods, but those methods should belong to the same responsibility.
2. Why do we need SRP?
Suppose we have an employee application.
We create one class called:
EmployeeService
and put everything inside it:
EmployeeService
│
├── Add Employee
├── Calculate Salary
├── Save Employee to Database
├── Send Email
└── Generate Report
This class is doing too many different jobs.
Now imagine these changes:
- Database changes from SQL Server to another database
- Email provider changes
- Salary calculation changes
- Report format changes
Every change requires us to modify the same EmployeeService.
That means:
EmployeeService
↓
Too many responsibilities
↓
Too many reasons to change
↓
Harder to maintain
↓
Higher chance of bugs
SRP tells us to separate these responsibilities.
3. Simple Scenario
Let's build a simple Order Processing application.
Our requirements are:
- Calculate order total.
- Save the order.
- Send confirmation email.
First, let's see a design that violates SRP.
4. Before SRP — Bad Design
using System;
public class OrderService
{
public void ProcessOrder(string customerEmail, decimal amount)
{
// Responsibility 1: Calculate total
decimal tax = amount * 0.10m;
decimal total = amount + tax;
Console.WriteLine($"Order Total: {total}");
// Responsibility 2: Save order
Console.WriteLine("Order saved to database.");
// Responsibility 3: Send email
Console.WriteLine(
$"Confirmation email sent to {customerEmail}");
}
}
public class Program
{
public static void Main()
{
OrderService orderService = new OrderService();
orderService.ProcessOrder(
"customer@gmail.com",
1000);
}
}
Output
Order Total: 1100.00
Order saved to database.
Confirmation email sent to customer@gmail.com
The program works.
But the design has a problem.
5. What is the problem?
OrderService is doing three different responsibilities:
OrderService
│
┌────────────┼────────────┐
↓ ↓ ↓
Calculate Database Email
Total Save Send
So there are three different reasons why OrderService might need to change.
Reason 1 — Business calculation changes
Today:
Tax = 10%
Tomorrow:
Tax = 18%
We modify OrderService.
Reason 2 — Database changes
Today:
SQL Server
Tomorrow:
Azure SQL
Again, we modify OrderService.
Reason 3 — Email changes
Today:
Gmail SMTP
Tomorrow:
SendGrid
Again, we modify OrderService.
So:
OrderService
│
┌────────────────┼────────────────┐
↓ ↓ ↓
Tax calculation Database Email
changes changes changes
│ │ │
└────────────────┼────────────────┘
↓
Modify OrderService
This is what SRP tries to avoid.
6. Apply Single Responsibility Principle
We separate the responsibilities into different classes.
OrderCalculator
↓
Calculate order total
OrderRepository
↓
Save order
EmailService
↓
Send email
OrderService
↓
Coordinate order processing
Each class now has a clear responsibility.
7. Full Simple Program Using SRP
using System;
// Responsibility 1:
// Calculate order total
public class OrderCalculator
{
public decimal CalculateTotal(decimal amount)
{
decimal tax = amount * 0.10m;
return amount + tax;
}
}
// Responsibility 2:
// Save order
public class OrderRepository
{
public void Save(decimal total)
{
Console.WriteLine(
$"Order saved. Total: {total}");
}
}
// Responsibility 3:
// Send email
public class EmailService
{
public void SendConfirmation(
string email,
decimal total)
{
Console.WriteLine(
$"Confirmation email sent to {email}. Total: {total}");
}
}
// Responsibility:
// Coordinate the order process
public class OrderService
{
private readonly OrderCalculator _calculator;
private readonly OrderRepository _repository;
private readonly EmailService _emailService;
public OrderService(
OrderCalculator calculator,
OrderRepository repository,
EmailService emailService)
{
_calculator = calculator;
_repository = repository;
_emailService = emailService;
}
public void ProcessOrder(
string customerEmail,
decimal amount)
{
decimal total =
_calculator.CalculateTotal(amount);
Console.WriteLine(
$"Order Total: {total}");
_repository.Save(total);
_emailService.SendConfirmation(
customerEmail,
total);
}
}
// Program
public class Program
{
public static void Main()
{
OrderCalculator calculator =
new OrderCalculator();
OrderRepository repository =
new OrderRepository();
EmailService emailService =
new EmailService();
OrderService orderService =
new OrderService(
calculator,
repository,
emailService);
orderService.ProcessOrder(
"customer@gmail.com",
1000);
}
}
Output
Order Total: 1100.00
Order saved. Total: 1100.00
Confirmation email sent to customer@gmail.com. Total: 1100.00
8. Program Explanation
Now let's understand the responsibility of each class.
OrderCalculator
public class OrderCalculator
{
public decimal CalculateTotal(decimal amount)
{
decimal tax = amount * 0.10m;
return amount + tax;
}
}
Its only responsibility is:
Order calculation
It doesn't know anything about:
- Database
- Saving orders
If tax calculation changes, we mainly change this class.
9. OrderRepository
public class OrderRepository
{
public void Save(decimal total)
{
Console.WriteLine(
$"Order saved. Total: {total}");
}
}
Its responsibility is:
Saving the order
In a real application, this class might use:
- Entity Framework Core
- SQL Server
- Dapper
It doesn't calculate tax.
It doesn't send emails.
10. EmailService
public class EmailService
{
public void SendConfirmation(
string email,
decimal total)
{
Console.WriteLine(
$"Confirmation email sent to {email}. Total: {total}");
}
}
Its responsibility is:
Sending email
If the email implementation changes, we modify the email-related class rather than putting that logic inside the order calculation or database code.
11. What about OrderService?
You may ask:
OrderServiceis calling three classes. Doesn't that mean it has three responsibilities?
No.
Its responsibility is:
Coordinate the order-processing workflow.
public void ProcessOrder(
string customerEmail,
decimal amount)
{
decimal total =
_calculator.CalculateTotal(amount);
_repository.Save(total);
_emailService.SendConfirmation(
customerEmail,
total);
}
It does not itself perform all those technical responsibilities.
It coordinates them.
Think about a manager.
A manager may tell:
Accountant → Calculate amount
Database team → Save information
Email team → Send confirmation
The manager's responsibility is coordination, not performing every specialist's job.
12. Flow of the Program
Program
│
↓
OrderService.ProcessOrder()
│
├───────────────→ OrderCalculator
│ │
│ ↓
│ Calculate Total
│ │
│ ↓
│ 1100.00
│
├───────────────→ OrderRepository
│ │
│ ↓
│ Save Order
│
└───────────────→ EmailService
│
↓
Send Confirmation
The important point is that each component has a clear purpose.
13. Before SRP vs After SRP
Before
OrderService
│
┌───────────┼───────────┐
↓ ↓ ↓
Calculate Save Email
OrderService contains all the logic.
After
OrderService
│
┌───────────────┼───────────────┐
↓ ↓ ↓
OrderCalculator OrderRepository EmailService
│ │ │
↓ ↓ ↓
Calculate Save Email
Now responsibilities are separated.
14. Does SRP Mean "One Method Per Class"?
No.
This is a common misunderstanding.
For example:
public class EmployeeRepository
{
public void Add()
{
}
public void Update()
{
}
public void Delete()
{
}
public void GetById()
{
}
}
This class contains several methods.
That doesn't automatically violate SRP because they all belong to one responsibility:
Employee data persistence
So remember:
One class = One method
is wrong.
Instead:
One class = One cohesive responsibility
is the better idea.
15. "One Reason to Change" — Important Interview Concept
This is the most important part of SRP.
Consider:
InvoiceService
If it handles:
Calculate Invoice
Save Invoice
Generate PDF
Send Email
then it can change because of:
Accounting rules changed
Database changed
PDF format changed
Email provider changed
That's multiple independent reasons to change.
After SRP:
InvoiceCalculator
↓
Accounting changes
InvoiceRepository
↓
Database changes
InvoicePdfGenerator
↓
PDF changes
EmailService
↓
Email changes
Now each class has a much clearer reason to change.
16. Real-Time ASP.NET Core Example
In an ASP.NET Core application, you might see:
OrderController
│
↓
OrderService
│
├──→ OrderRepository
│
├──→ PaymentService
│
└──→ EmailService
Responsibilities could be:
| Class | Responsibility |
|---|---|
OrderController |
Handle HTTP request/response |
OrderService |
Coordinate order business workflow |
OrderRepository |
Order database operations |
PaymentService |
Payment processing |
EmailService |
Email communication |
This separation is common in real enterprise applications.
17. Advantages of SRP
1. Easy to Maintain
If email logic changes:
Change EmailService
You don't need to modify unrelated order calculation logic.
2. Easy to Test
You can test:
OrderCalculator
separately from:
EmailService
OrderRepository
This makes unit testing simpler.
3. Easy to Debug
If the order total is wrong, you know to investigate:
OrderCalculator
If email is not working:
EmailService
The responsibility boundary helps narrow down problems.
4. Reduces Coupling
Classes have fewer unrelated dependencies and concerns.
This generally makes the system easier to change.
5. Better Readability
Compare:
OrderService
├── Calculate
├── Database
├── Email
├── PDF
├── Logging
├── SMS
└── Payment
with:
OrderCalculator
OrderRepository
EmailService
PdfService
PaymentService
The second design communicates responsibilities more clearly.
6. Easier Team Development
Different developers can work on different areas:
Developer A → PaymentService
Developer B → OrderRepository
Developer C → EmailService
Clear boundaries can reduce unnecessary overlap.
7. Easier Future Changes
Suppose tomorrow:
10% Tax
↓
18% Tax
The calculation-related class is the natural place to make that change.
18. Can SRP Be Overused?
Yes.
You should not create a separate class for every tiny method just because SRP exists.
Bad interpretation:
AddEmployeeClass
UpdateEmployeeClass
DeleteEmployeeClass
GetEmployeeClass
That may create unnecessary complexity.
Instead, if all these operations belong naturally together:
EmployeeRepository
├── Add()
├── Update()
├── Delete()
└── Get()
can still have a single cohesive responsibility:
Employee persistence
SRP is about responsibility, not the number of methods.
19. How to Identify an SRP Violation
When reviewing a class, ask:
Question 1:
What is this class responsible for?
If your answer contains many unrelated responsibilities, investigate further.
For example:
"It calculates invoices, saves them, sends email, generates PDF and writes audit logs."
That is a warning sign.
Question 2:
What different business or technical changes could force this class to change?
If you find many unrelated reasons, the class may violate SRP.
20. Key Points
- SRP = Single Responsibility Principle.
- It is the S in SOLID.
- A class should have one primary reason to change.
- SRP does not mean one method per class.
- Multiple related methods can exist in the same class.
- Separate unrelated responsibilities such as business calculation, database access, email, reporting, and file generation.
- SRP improves maintainability, readability, testability, and change isolation.
- A service can coordinate several other services and still follow SRP when coordination itself is its responsibility.
- Don't split classes unnecessarily; the goal is high cohesion, not maximum number of classes.
Interview Answer
Single Responsibility Principle states that a class should have only one reason to change. In simple terms, each class should focus on one cohesive responsibility. For example, order calculation, database persistence, and email notification should generally be handled by separate components rather than putting all the logic into one class. This makes the application easier to maintain, test, debug, and extend.