Liskov Substitution

 

So far, we have seen the single responsibility and open/closed principles; in this post, we will cover the third of the SOLID principles, the Liskov substitution principle.

 

What is the Liskov substitution principle

The reason this principle is called the “Liskov substitution principle” is because it was described by a woman named Barbara Liskov in the 70s, but she did so in a very mathematical way in a paper that is mostly algebra, which is why Robert C. Martin redefined it with the following phrase: "subtypes must be substitutable for their base types."

 

Still, it remains a very abstract description and is not easily understood. In short, the Liskov substitution principle is about polymorphism. In the real world, the use of polymorphism must be done with caution as it is a powerful yet complex tool and can lead us to a dead end.

A very simple way to understand the Liskov substitution principle is by showing an opposite example.

imagen liskovAs we can see, both are ducks, both quack, but one needs batteries, meaning we have an incorrect abstraction.

When we apply the Liskov substitution principle, there is no general way to do it; it depends entirely on our application and its specific behavior. There is, however, one rule: "The system must not break."

In summary: If we have a client/service calling interface A and we change the client to call implementation B of the same interface, the system should not break.

 

Violation of the Liskov substitution principle

One way to see that you are violating the Liskov substitution principle is by throwing a NotImplementedException. Obviously, this situation should never happen in production.

public void Metodo1(){
    throw new NotSupportedException();
}

The most common way to violate the Liskov substitution principle is by trying to extract interfaces. When you have a class and want to extract its interface, you need to consider each method and property to extract, and avoid extracting all of them. You should only extract those that you want to keep public.

 

Example of the Liskov substitution principle

For this example, we are going to use the same code we saw in the previous post about the open/closed principle, which is available for download from GitHub.

For the example, let’s modify the Almacenamiento class, call it AlmacenamientoFichero and add a method called InformacionFichero to show the example of a violation of the aforementioned principle.

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

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

    public FileInfo InformacionFichero(string titulo)
    {
        return new FileInfo($"{path}/{titulo}.txt");
    }
}

In our example, we want to change the system so that instead of saving to a file, it saves to a database. Therefore, we create a class that implements the same methods as AlmacenamientoFichero.

public class AlmacenamientoSQL
{
    public void Guardar(string titulo, string contenido)
    {
    }

    public string Leer(string titulo)
    {
    }

    public FileInfo InformacionFichero(string titulo)
    {
    }
}

As we see, we are implementing the same methods as in our AlmacenamientoFichero class, but now we are not working with files, so it would be advisable to change the method names.

public class AlmacenamientoFichero
{
    readonly string path = "C:/temp";
    public void Guardar(string titulo, string contenido)
    {
        //code
    }

    public string Leer(string titulo)
    {
        //code
    }

    public FileInfo InformacionFichero(string titulo)
    {
        //code
    }
}

public class AlmacenamientoSQL
{
    public void Guardar(string titulo, string contenido)
    {
        //code
    }

    public string Leer(string titulo)
    {
        //code
    }

    public FileInfo InformacionFichero(string titulo)
    {
        throw new NotSupportedException();
    }
}

To ensure we’re specifying everything correctly, we should extract an interface from our first class:

public interface IAlmacenamiento
{
    void Guardar(string titulo, string contenido);
    string Leer(string titulo);
    FileInfo InformacionFichero(string titulo);
}

public class AlmacenamientoSQL : IAlmacenamiento{}
public class AlmacenamientoFichero : IAlmacenamiento{}

As we see, we are specifying a method that will not be used, because when saving to SQL we cannot get the file information. This is the violation mentioned above. That method should not be declared in the interface.

Additionally, we throw an exception in that method since we can never execute it if we are querying the database.

Of course, in our main class ArticulosServicio we should replace the storage with the IAlmacenamiento interface, and I’ve changed the code a little to check that the file exists before reading it.

public class ArticulosServicio
{
    private readonly Cache _cache;
    private readonly ILogging _logging;
    private readonly IAlmacenamiento _almacenamiento;

    public ArticulosServicio()
    {
        _logging = new DatabaseLog();
        _almacenamiento = new AlmacenamientoFichero();
        _cache = new Cache();
    }

    public void GuardarArticulo(string contenido, string titulo)
    {
        _logging.Info($"vamos a insertar el articulo {titulo}");
        _almacenamiento.Guardar(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}");

        if (!_almacenamiento.InformacionFichero(titulo).Exists)
        return null;

        return _almacenamiento.Leer(titulo);

    }
}

As we can see, we have a problem with our LeerInformacionFichero method because it doesn’t make any sense. But since we made a bad interface implementation, we can’t remove the method.

As mentioned, due to this method, the code will break when executed. To fix it, we can change the implementation. We could return a simulated file, indicate a fake file, etc., but this is not a good solution, as it might lead to false positives. As we see, this interface implementation is very problematic since it only works when working with the file system.

How do we fix this scenario?

Clearly, the final goal is to remove the LeerInformacionFichero method from the IAlmacenamiento interface.

To do so, we have to change the logic that our AlmacenamientoFichero class is executing, and modify the Leer method so that it tells us if the file exists before we try to read it.

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

    public string Leer(string titulo)
    {
        if (!InformacionFichero(titulo).Exists)
         return null;
         
        return File.ReadAllText($"{path}/{titulo}.txt");
    }

    private FileInfo InformacionFichero(string titulo)
    {
        return new FileInfo($"{path}/{titulo}.txt");
    }
}

Obviously, this example is very simple, but in a real example it could be much more complex.

public interface IAlmacenamiento
{
    void Guardar(string titulo, string contenido);
    string Leer(string titulo);
}

 

Conclusion

My advice to avoid these situations is that whenever you are developing and want to create a class, think about its implementation and how you could extract it into an interface if you ever want to change it in the future.

The SOLID principles are highly related to each other and it's very difficult to explain one of them 100% without referring to, or mentioning, another one.

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é