Definition
According to Wikipedia the Single Responsibility Principle is a computer program principle that states that “A module should be responsible to one, and only one, actor”. Robert C. Martin, the originator of the term, expresses the principle as “A class should have only one reason to change”. In my experience as an interviewer, I realized that the candidates had trouble recognizing the pattern in code. In other words, they lack a heuristic that would inform them whether the principle is respected or violated inside a module, be it a class or even a method. So, to tease out the details I propose the following example.
The Example
Let’s imagine a small method whose responsibility is to compute an average. The method retrieves a list of users from the database and proceeds to computing the average of one of their properties (for the sake of simplicity let’s assume the age).
The method might look something like this:
1. public static class StatisticsCalculator
2. {
3. public static decimal AgeAverageOfFirstFiveUsers()
4. {
5. /*
6. * Get users from database
7. */
8. IEnumerable<User> users = new List<User>
9. {
10. new(23),
11. new(78),
12. new(96),
13. new(42),
14. new(19)
15. };
16.
17. var count = users.Count();
18. var sum = 0;
19.
20. foreach (var user in users) sum += user.Age;
21.
22. return (decimal)sum / count;
23. }
24. }
25.
26. public record struct User(int Age);
How many responsibilities does this method have? Well let’s analyze it:
- First, it knows how to obtain a list of users. It knows how to call the database and retrieve the users.
- Second, once it has the list of users, it knows what to do with them. It knows how to extract the age information and use that in some logic that calculates an average.
So, at first glance looks like it has at least two responsibilities.
Obviously then, we can say that this method does not respect the Single Responsibility Principle.
What can we do to make it respect Single Responsibility Principle?
One Solution
Well, the task is to reduce the number of responsibilities from two to a single one. The idea is to extract out the two functionalities (reading from the database and calculating the average) so that they are no longer the responsibility of our method. Now you might be confused and say: “Well, if we extract all the functionality from the method, then we’ll end up with zero responsibilities, not one. How is this a good idea?” Hopefully, the answer will reveal itself and by the end I will have convinced you it’s a great idea.
Extract Data Access
It doesn’t matter with which we start because eventually we want to extract both responsibilities, but let’s start with the database.
I’ll create a repository for the users that will encapsulate the database access logic and will provide us with an IQueryable<User> output. Our method will use the repository to retrieve the first five users from the database.
This is what the repository and its interface might look like.
public interface IUserRepository
{
IQueryable<User> GetUsers();
}
public class UserRepository : IUserRepository
{
public IQueryable<User> GetUsers()
{
/*
* Get users from database
*/
var users = new List<User>
{
new(23),
new(78),
new(96),
new(42),
new(19),
new(27),
new(61),
new(49)
};
return users.AsQueryable();
}
}
What we have to do now is to use the repository instead of accessing the database directly.
1. internal class StatisticsCalculator
2. {
3. private readonly IUserRepository userRepository;
4.
5. public StatisticsCalculator(IUserRepository userRepository)
6. => this.userRepository = userRepository;
7.
8. public decimal AgeAverageOfFirstFiveUsers()
9. {
10. var users = userRepository.GetUsers().Take(5).ToList();
11.
12. var count = users.Count;
13. var sum = 0;
14.
15. foreach (var user in users) sum += user.Age;
16.
17. return (decimal)sum / count;
18. }
19. }
That’s one responsibility extracted, let’s move to the other one.
Extract The Average
I will proceed in a similar manner and define a class that calculates the average and define an interface for it.
1. public interface IAverageCalculator
2. {
3. decimal GetAverage(IEnumerable<int> numbers);
4. }
5.
6. public class AverageCalculator : IAverageCalculator
7. {
8. public decimal GetAverage(IEnumerable<int> numbers)
9. {
10. var usersList = users.ToList();
11.
12. var count = usersList.Count;
13. var sum = 0;
14.
15. foreach (var user in usersList) sum += user.Age;
16.
17. return (decimal)sum / count;
18. }
19. }
After injection, our class looks like this:
1. public class StatisticsCalculator
2. {
3. private readonly IAverageCalculator averageCalculator;
4. private readonly IUserRepository userRepository;
5.
6. public StatisticsCalculator(IUserRepository userRepository,
7. IAverageCalculator averageCalculator)
8. {
9. this.userRepository = userRepository;
10. this.averageCalculator = averageCalculator;
11. }
12.
13. public decimal AgeAverageOfFirstFiveUsers()
14. {
15. var users = userRepository.GetUsers().Take(5);
16.
17. return averageCalculator.GetAverage(users.Select(x => x.Age));
18. }
19. }
This is fine, but…
You would say we are in a better place than when we started. And I would agree. For one thing the code is easier to read and understand.
But while readability and ease of understanding are important qualities, they are not the only qualities code must have.
The first issue would be that we would have to ensure the IEnumerable that GetAverage method receives contains the exact same users that were returned by the GetUsers method. Since the users list undergoes a mapping (technically it’s a projection) before being used in the GetAverage method that means our method has at least one more responsibility: that of mapping data.
In order to fix this issue, we have to remember another principle that states that the interface is owned by the client. Because of that we can improve the interface signature to serve exactly the needs of the client, in this case our method. The purpose of the interface we created, namely IAverageCalculator, is to best serve our needs, and enforce our contract (functionality and accompanying data structures) in the implementing classes.
The technical implementation is to change the GetAverage method to accept directly an IEnumerable of users, without us having to worry about creating the appropriate projection. This enforces the fact that the interface is defined by our method as the client and the collaborator class must respect the contract.
The same logic applies to the repository class. Given the interface is owned by the client we can afford to expose an explicit method that reflects exactly the functionality needed by the client: return only the first five users.
1. public interface IUserRepository
2. {
3. IEnumerable<User> GetFirstFiveUsers();
4. }
The repository is free to implement this functionality however it sees fit. Therefore, our method now looks like this:
1. public class StatisticsCalculator
2. {
3. private readonly IUserAgeAverageCalculator averageCalculator;
4. private readonly IUserRepository userRepository;
5.
6. public StatisticsCalculator(IUserRepository userRepository,
7. IUserAgeAverageCalculator averageCalculator)
8. {
9. this.userRepository = userRepository;
10. this.averageCalculator = averageCalculator;
11. }
12.
13. public decimal AgeAverageOfFirstFiveUsers()
14. {
15. var users = userRepository.GetFirstFiveUsers();
16.
17. return averageCalculator.GetAverage(users);
18. }
19.
20. public interface IUserRepository
21. {
22. IEnumerable<User> GetFirstFiveUsers();
23. }
24.
25. public interface IUserAgeAverageCalculator
26. {
27. decimal GetAverage(IEnumerable<User> users);
28. }
29. }
Notice I introduced the two interfaces inside our class. This is to enforce the fact the ownership belongs to our class and these interfaces should not be used anywhere else. I admit this is not strictly needed, but I won’t miss an opportunity to be pedantic.
But Wait, There’s More
Actually, let’s take a look at the AverageCalculator class. I’ll just paste it here for convenience.
1. public class AverageCalculator : IAverageCalculator
2. {
3. public decimal GetAverage(IEnumerable<int> numbers)
4. {
5. var usersList = users.ToList();
6.
7. var count = usersList.Count;
8. var sum = 0;
9.
10. foreach (var user in usersList) sum += user.Age;
11.
12. return (decimal)sum / count;
13. }
14. }
Notice anything funny going on? Yes, you’re right, it has two responsibilities.
It mixes the knowledge of how to calculate the average with the knowledge of how to get the data it is calculating the average on. It implicitly uses a projection to extract the age of each user, projection that is used inside the average calculating logic.
We would like to separate these two concerns: the average can be calculated on any sequence of numbers not just specifically the average. As long as we can get a hold of a list of numbers, we should be able to calculate the average.
So basically, it decides what property to choose from a given object and then does something with it. So, applying the same logic of extracting out responsibilities we transform this class/method into an orchestrator and provide it with appropriate collaborators.
I won’t drag this out any more than necessary and I’ll just paste the full (and final) version of the code.
1. public class StatisticsCalculator
2. {
3. private readonly IUserAgeAverageCalculator userAverageCalculator;
4. private readonly IUserRepository userRepository;
5.
6. public StatisticsCalculator(IUserRepository userRepository,
7. IUserAgeAverageCalculator userAverageCalculator)
8. {
9. this.userRepository = userRepository;
10. this.userAverageCalculator = userAverageCalculator;
11. }
12.
13. public decimal AgeAverageOfFirstFiveUsers() =>
14. userAverageCalculator.GetAgeAverage(userRepository.GetFirstFiveUsers());
15.
16. public interface IUserRepository
17. {
18. IEnumerable<User> GetFirstFiveUsers();
19. }
20.
21. public interface IUserAgeAverageCalculator
22. {
23. decimal GetAgeAverage(IEnumerable<User> users);
24. }
25. }
26.
27. public class UserRepository : StatisticsCalculator.IUserRepository
28. {
29. public IEnumerable<User> GetFirstFiveUsers()
30. {
31. /*
32. * Get users from database
33. */
34. var users = new List<User>
35. {
36. new(23),
37. new(78),
38. new(96),
39. new(42),
40. new(19)
41. };
42.
43. return users;
44. }
45. }
46.
47. public class UserAgeAverageCalculator : StatisticsCalculator.IUserAgeAverageCalculator
48. {
49. private readonly IAverageCalculator averageCalculator;
50. private readonly IUserAgeProjector userAgeProjector;
51.
52. public UserAgeAverageCalculator(IUserAgeProjector userAgeProjector,
53. IAverageCalculator averageCalculator)
54. {
55. this.userAgeProjector = userAgeProjector;
56. this.averageCalculator = averageCalculator;
57. }
58.
59. public decimal GetAgeAverage(IEnumerable<User> users) =>
60. averageCalculator.GetAverage(userAgeProjector.UserAges(users));
61.
62. public interface IAverageCalculator
63. {
64. public decimal GetAverage(IEnumerable<int> numbers);
65. }
66.
67. public interface IUserAgeProjector
68. {
69. IEnumerable<int> UserAges(IEnumerable<User> usersList);
70. }
71. }
72.
73. public class UserAgeProjector : UserAgeAverageCalculator.IUserAgeProjector
74. {
75. public IEnumerable<int> UserAges(IEnumerable<User> usersList)
76. => usersList.Select(x => x.Age).ToList();
77. }
78.
79. public class AverageCalculator : UserAgeAverageCalculator.IAverageCalculator
80. {
81. public decimal GetAverage(IEnumerable<int> userAges)
82. {
83. var enumerable = userAges.ToList();
84.
85. var count = enumerable.Count;
86. var sum = 0;
87.
88. foreach (var age in enumerable) sum += age;
89.
90. return (decimal)sum / count;
91. }
92. }
93.
94. public record struct User(int Age);
For your convenience I’ll even throw into the bargain a diagram that highlights the dependencies between the classes.

Conclusion
I’ll admit, maybe I went a bit overboard. Maybe this seems like overkill. And it certainly is for such a small example. I just wanted to highlight what the principle was about and what will happen to the code if we follow it to its logical conclusion.
In the end the Single Responsibility Principle is just that, a principle. Ignore it at your own peril, but at least do it consciously. Like anything in life, it must be used in moderation (i.e., only where it helps and improves things).