S.O.L.I.D. - Single Responsibility Principle
Source - Google Images

S.O.L.I.D. - Single Responsibility Principle

A class should have only one reason to change. — Robert C. Martin

This article is part of a series of 5 articles, each one explaining a single principle of the S.O.L.I.D. design principles.

Too many of us, developers, often think that an application is ready once the program works. We often forget to switch to that additional task regarding the organisation and cleaning our code.

We move to the next assignment leaving the code that we just wrote as it is. It proves to be working, so why would we spend more time on this task rather than switching to a new, exiting task?

Imagine that we’re working on a Database Driven application and we’re assigned with the task to create a user. When the User entity is created we want to write a message to the terminal indicating that the user has been created.

The code below does exactly what’s described in the requirements.

public sealed class UserRepository
{
    public void Create(UserCreateModel model)
    {
        // Code to create the 'User' entity in the DB.
	        
        Console.WriteLine("[SUCCESS]: The 'User' has ...");
    }
}

Even though this class has only 1 method, this class violates the Single Responsibility Principle since it does have 2 responsibilities.

  • It creates a ‘User’ entity
  • It prints a statement to the terminal

Why is this considered to be a problem?

Developers often choose the path that contains the less resistance when they are dealing with a given task.

Imagine that management assigns us a task to create another entity in the database, such as a Blog Post. As developers, we search in the code if there’s some similar functionality because if there is, there is code that already proves to be working thus we can just re-use that piece of code.

public sealed class BlogPostRepository
{
    public void Create(BlogPostCreateModel model)
    {
        // Code to create the 'Blog Post' entity in the DB.
	        
        Console.WriteLine("[SUCCESS]: The 'Blog Post' has ...");
    }
}

As the application evolves over time, we might have several ‘Repository’ classes that handles saving some entities in the database.

At some moment in time, the DEV team is being asked to implement a “real” logging framework such as SEQ for logging instead of writing to the terminal. According to the management this is a very simple task since it’s only changing the location of ‘Log Entries’ from the terminal to SEQ.

But since the Repository classes are violating the Single Responsibility Principle, the logging mechanism scattered throughout the whole code base.

Again, as developers we choose the path that contains the less resistance, so we just implement the logging mechanism using SEQ in a single Repository class to and we test that implementation. If the code proves to be working, we copy it to all the other locations where it’s needed and if, and only if, we have some time left, we might test all the other locations.

This is true for developers that are familiar with the design of the application.

Image right now that a bug is reported with the logging mechanism when creating a user. A developer that’s not familiarized with the design of the application will search in the code base for the location where a User is being created and starts to investigate the bug and eventually also fix it in this class, being unaware that the same bug is present in all the other Repository classes. This will lead to inconsistencies in the application.

How to fix this violation?

To fix this violation every class is allowed to have only one single responsibility. So let’s extract the logging mechanism into a separate class.

public sealed class Logger
{
    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }
}

With the logic to write log messages in a separate class, we can update the Repository classes to use this logger class.

public sealed class UserRepository
{
    private readonly Logger logger = new Logger();
	    
    public void Create(UserCreateModel model)
    {
	// Code to create the 'User' entity in the DB.
	        
	this.logger.WriteLine("[SUCCESS]: The 'User' has ...");
    }   
}

Be aware that code above is not considered “good” code. There still are violations against other S.O.L.I.D. principles but these will be handled in other articles.

Conclusion

Adhering to the Single Responsibility Principle means that you’ll be writing more code, but the code that you write will be far easier to understand.

Nice post! Another strategy to handle errors - which I prefer for most situations - is to not handle them at all, but just letting them bubble up to the caller.

To view or add a comment, sign in

More articles by Kevin De Coninck

  • C# Unit Testing - IServiceCollection

    The ‘IServiceCollection’ class, is Microsoft’s standard way to provide Dependency Injection in a .NET Core application.

    3 Comments

Others also viewed

Explore content categories