The Single Responsibility Principle in C#: The S of SOLID

Erik Zhou | Mar 25, 2023 min read

The Single Responsibility Principle (SRP) is one of the five principles of object-oriented programming and design known as SOLID. In this blog post, we will take a look into the SRP, its benefits, and its implementation in C#.

What is the Single Responsibility Principle?

The Single Responsibility Principle, defined by Robert C. Martin, states that “A class should have one, and only one, reason to change.” In simpler terms, a class should have only one job or one “responsibility”, get it? This principle helps in designing a maintainable and modular system by ensuring that each class has a well-defined purpose.

Why is SRP important?

Sticking to the SRP offers several benefits:

  1. Easier maintainability: Classes with a single responsibility are easier to understand, change, and debug. They are less likely to introduce bugs when changed since they have a limited scope.
  2. Improved testability: A class with a single responsibility is easier to test, as it has a smaller number of test cases to cover.
  3. Better reusability: A class with a well-defined purpose is more likely to be reused, as it can be easily understood and incorporated into other parts of the system.
  4. Reduced coupling: Following the SRP reduces the number of dependencies between classes, making the overall system less coupled and more flexible.

Implementing SRP in C#

Let’s go through an example to see how we can apply the SRP in a C# application.

A violation of the SRP

Imagine we have a simple application that processes customer orders. We have a class called OrderProcessor that reads orders from a file, validates them, and then saves them to a database. The class looks like this:

public class OrderProcessor
{
    public void ProcessOrders(string filePath)
    {
        var orders = File.ReadAllLines(filePath);
        foreach (var order in orders)
        {
            if (IsValid(order))
            {
                SaveOrderToDatabase(order);
            }
        }
    }

    private bool IsValid(string order)
    {
        // Validate the order
    }

    private void SaveOrderToDatabase(string order)
    {
        // Save the order to the database
    }
}

The OrderProcessor class violates the SRP because it has multiple responsibilities: reading orders from a file, validating orders, and saving orders to the database. This makes the class harder to maintain, test, and reuse.

Refactoring to adhere to the SRP

To adhere to the SRP, we can split the OrderProcessor class into smaller classes, each with a single responsibility. For example:

public class OrderReader
{
    public IEnumerable<string> ReadOrders(string filePath)
    {
        return File.ReadAllLines(filePath);
    }
}

public class OrderValidator
{
    public bool IsValid(string order)
    {
        // Validate the order
    }
}

public class OrderSaver
{
    public void SaveOrderToDatabase(string order)
    {
        // Save the order to the database
    }
}

public class OrderProcessor
{
    private readonly OrderReader _orderReader;
    private readonly OrderValidator _orderValidator;
    private readonly OrderSaver _orderSaver;

    public OrderProcessor(OrderReader orderReader, OrderValidator orderValidator, OrderSaver orderSaver)
    {
        _orderReader = orderReader;
        _orderValidator = orderValidator;
        _orderSaver = orderSaver;
    }

    public void ProcessOrders(string filePath)
    {
        var orders = _orderReader.ReadOrders(filePath);
        foreach (var order in orders)
        {
            if (_orderValidator.IsValid(order))
            {
                _orderSaver.SaveOrderToDatabase(order);
            }
        }
    }
}

Now, we have four classes, each with a single responsibility. The OrderReader class is responsible for reading orders from a file, the OrderValidator class is responsible for validating orders, the OrderSaver class is responsible for saving orders to the database, and the OrderProcessor class is responsible for coordinating the entire process.

This refactored design is easier to maintain, test, and reuse. Additionally, it reduces coupling between the different responsibilities, making the system more flexible and adaptable to changes.

You may also notice that this looks a lot like dependency injection. That’s because it is, click the link to learn more.

Conclusion

The Single Responsibility Principle is a fundamental principle of object-oriented programming and design that promotes maintainability, testability, reusability, and reduced coupling. By ensuring that each class in your C# application has only one responsibility, you can create a more robust and modular system that is easier to understand, change, and extend.