Single Responsibility Principle (SRP) in C#
In my previous article I wrote about what they are and why to use Solid Principles in C#.
In this article, I am going to show you when and how to use the Single Responsibility Principle in C# with an example project. You can find the repository on GitHub.
The master branch shows the initial code used in the example. There are separate tags and branches for each of the all solid principles that you can review or download as well. Here are links you can use to jump to these tagged versions in your browser:
What is the Single Responsibility Principle (SRP) in C#?
The Single Responsibility Principle is one of the SOLID design principles. We can reuse the definition from Wikipedia. The single-responsibility principle (SRP) is a computer-programming principle that states that every module, class or function in a computer program should have responsibility over a single part of that program’s functionality, and it should encapsulate that part. Robert C. Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to be changed.
To better understand it, we need to divide the definition into two parts:
- Single Responsibility A class/method/function should have only a single responsibility.
- A reason to change A class or method should have only one reason to change. About the “reason” Martin clarified that this principle is about people/role. It is people who request changes.
Why should you use the Single Responsibility Principle (SRP)?
- Reduction in complexity of a code. A code is based on its responsibility and functionality. So, it reduces the code complexity.
- Increased readability, extensibility and maintenance. As each method has a single functionality so it is easy to read and maintain.
- Reusability and reduced error. As code separates based functionality so you can reuse the code somewhere else in an application.
- Reduced coupling. It reduced the dependency code. A method’s code doesn’t depend on other methods.
- Better testability. In the maintenance, when a functionality changes then we don’t need to test the entire model.
Let’s take a look at the example code and in particular to the method Evaluate() of the DeviceService class, to identify and apply the Single Responsibility Principle. This method should only evaluate a device to know if it’s a best buy product.
How many responsibilities do you find?
- Persistence responsibility, what will happen if we want to change file format or move from file system to a web api?
string dataJson = File.ReadAllText("deviceData.json");
- Logging responsibility, what will happen if we want to move to a different logging framework?
Console.WriteLine("Evaluating Mobile device...");
- Encoding Format responsibility, what will happen if we want to move from json to yaml or xml?
var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); var device = JsonSerializer.Deserialize<Device>(dataJson, options);
- Business Rules responsibility, what will happen if we want to add a new Device Type?
switch (device.Type) { case DeviceType.Mobile:
- Validation responsibility, what will happen if we want to change the model?
if (String.IsNullOrEmpty(device.Sim)) {
How we can refactor the code applying the Single Responsibility Principle (SRP)?
Mine is not the ultimate refactoring, I just want to give you an idea of how to apply it gradually by making incremental improvements. Let’s create new classes that does just one thing (one responsibility).
- Persistence responsibility, read device data from a json file.
public class FileDataSource { public string GetDeviceFromSource() { return File.ReadAllText("deviceData.json"); } }
- Logging responsibility, create a class just for logging.
public class ConsoleLog { public void WriteLine(string message) { Console.WriteLine(message); } }
- Encoding Format responsibility, create a class to deserialize a json string to an object.
public class JsonDataSerializer { public Device GetDeviceFromJsonString(string jsonString) { var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); return JsonSerializer.Deserialize<Device>(jsonString, options); } }
There are many more improvements to be made but look at how the method Evaluate() has changed and how easy it will be to test each individual class.
Let’s summarize what we said about the Single Responsibility Principle (SRP):
- Each class should have a single responsibility or a reason to change.
- Strive for high cohesion and loose coupling.
- Keep classes small, focused and testable.
If you think your friends/network would find this useful, please share it with them. I’d really appreciate it.
Thanks for reading!