Single Responsibility

 

In this post, we will look at what the Single Responsibility Principle or Single Responsibility Principle (SRP) is. 

 

1 - What is the Single Responsibility Principle?

The most famous description of this principle comes from its author, Robert C. Martin, who said, "A class should have only one reason to change." 

But what does this actually mean?

A crucial aspect when writing code is to ensure our code is easy to maintain and to read. The way to achieve this is by making sure each class does one thing and does it well

When we have classes that perform more than one task, they end up coupled with each other when they shouldn't, making those classes much harder to use, understand, and of course, maintain.

A benefit of keeping code with well-differentiated classes is that they are easier to test, which means it's much harder for bugs to occur. 

A quick example of how the Single Responsibility Principle works is Unix, since Unix was originally a command-line system where each command is a small script that does exactly what it is supposed to do, and does it correctly. 

 

2 - Example of the Single Responsibility Principle

To understand the concept behind the Single Responsibility Principle, the easiest thing is to see an example.

This example is extremely simplified for clarity. 

Let's say we have a class in our code with a method that allows us to insert an article and another method that allows us to read that article.

public void GuardarArticulo(string contenido, string titulo)
{
    Log.Information($"vamos a insertar el articulo {titulo}");
    File.WriteAllText($"{path}/{titulo}.txt", contenido);
    Log.Information($"articulo {titulo} insertado");
    this.Cache.Add(titulo, contenido);
}

public string ConsultarArticulo(string titulo)
{
    Log.Information($"Consultar artículo {titulo}");

    string contenido = this.Cache.Get(titulo);
    if (!string.IsNullOrWhiteSpace(contenido))
    {
        Log.Information($"Artículo encontrado en la cache {titulo}");
        return contenido;
    }

    Log.Information($"buscar articulo en el sistema de archivos {titulo}");
    contenido = File.ReadAllText($"{path}/{titulo}.txt");

    return contenido;
}

In these two methods, we should identify what factors or code would make us have to modify this class, and we can find the following points: 

  • We carry out multiple logs.
  • We store the article, in this case in the file system.
  • The cache system. 

As you can see, there are three points.

  • For example, we might want to change the logs, from serilog to log4net or even a custom option.
  • We might want to store the articles in a database instead of the file system.
  • If we want to change the cache system. 

For these reasons, the Single Responsibility Principle is not being met, as changing the logging system would mean changing a class that reads articles, which is not correct. 

But that's not all. In this example, there is a fourth reason: the logic of the methods themselves, i.e., how the three functionalities interact to provide us with the information we're looking for. 

 

2.1 - Refactoring code to comply with the Single Responsibility Principle

To comply with the Single Responsibility Principle, we need to take each of those reasons that might make us change the class and extract them into a new class.

For example, the logging system. We should separate it into a new class.

And this new class is the one responsible for ensuring that our logs are properly inserted, whether using serilog or log4net.

Using Serilog;
public class Logging
{
    public void Info(string message)
    {
        Log.Information(message);
    }
    public void Error(string message, Exception e)
    {
        Log.Error(e, message);
    }
    public void Fatal(string message, Exception e)
    {
        Log.Fatal(e, message);
    }
}

Then, we should indicate in our existing class that it should use this new class for logging and update the variables. 

public class ArticulosServicio
{

    private readonly Logging _logging;

    public ArticulosServicio()
    {
        _logging = new Logging();
    }

    public void GuardarArticulo(string contenido, string titulo)
    {
        _logging.Info($"vamos a insertar el articulo {titulo}");
        File.WriteAllText($"{path}/{titulo}.txt", contenido);
        _logging.Info($"articulo {titulo} insertado");
        this.Cache.Add(titulo, contenido);
    }
    //Resto del código
}

2.2 - How does this improve our code?

Now the question is to understand how this action improves our code.

In the example, I'm using serilog, which, when we want to log an exception, we would do it like this:

Log.Error(exception, message);

Whereas if we use log4net (another logging library), it works with a slight change.

Log.Error(message, exception);

 

serilog vs logfornet

 

This change means that if we want to change the way our application logs, we only need to do it once in the Logging class and not every single time we call logging throughout our code. 

We must do the same thing for each of the reasons or factors we identified for modifying our code. That means we need to create a class for logging, a class for managing the cache, and a class to access/create files. 

public class Cache
{
    private Dictionary<string, string> CacheDicctionary;
    public Cache()
    {
        CacheDicctionary = new Dictionary<string, string>();
    }

    public void Add(string titulo, string contenido)
    {
        if(!CacheDicctionary.TryAdd(titulo, contenido))
        {
            CacheDicctionary[titulo] = contenido;
        }
    }

    public string Get(string titulo)
    {
        CacheDicctionary.TryGetValue(titulo, out string contenido);
        return contenido;
    }

}

public class Almacenamiento
{
    string path="C:/temp";
    public void EscribirFichero(string titulo, string contenido)
    {
        File.WriteAllText($"{path}/{titulo}.txt", contenido);
    }

    public string LeerFichero(string titulo)
    {
        return File.ReadAllText($"{path}/{titulo}.txt");
    }
}

As you can imagine, if a class is very big, it could contain many additional classes it refers to. That's why SOLID includes dependency injection, which we will see later in this series of posts. This is the final result:

public class ArticulosServicio
{
    private readonly Cache _cache;
    private readonly Logging _logging;
    private readonly Almacenamiento _almacenamiento;

    public ArticulosServicio()
    {
        _logging = new Logging();
        _almacenamiento = new Almacenamiento();
        _cache = new Cache();
    }

    public void GuardarArticulo(string contenido, string titulo)
    {
        _logging.Info($"vamos a insertar el articulo {titulo}");
        _almacenamiento.EscribirFichero(titulo, contenido);
        _logging.Info($"articulo {titulo} insertado");
        _cache.Add(titulo, contenido);
    }

    public string ConsultarArticulo(string titulo)
    {
        _logging.Info($"Consultar artículo {titulo}");

        string contenido = _cache.Get(titulo);
        if (!string.IsNullOrWhiteSpace(contenido))
        {
            _logging.Info($"Artículo encontrado en la cache {titulo}");
            return contenido;
        }

        _logging.Info($"buscar articulo en el sistema de archivos {titulo}");

        return _almacenamiento.LeerFichero(titulo);
    }
}

 

Conclusion

  • When we apply the Single Responsibility Principle, our code becomes easier to understand because each class has a single, unique functionality.
  • The code will be easier to maintain, as changes will only affect the classes that are directly related, reducing the risk of breaking unrelated code.
  • Our code becomes more reusable because each class has a responsibility. If we want to access a certain functionality, we must go through the responsible class. 

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café