Logo

dev-resources.site

for different kinds of informations.

LSP - O Princípio da Substituição de Liskov

Published at
1/9/2025
Categories
liskov
solidprinciples
designpatterns
softwaredevelopment
Author
thrsouza
Author
8 person written this
thrsouza
open
LSP - O Princípio da Substituição de Liskov

O LSP (Liskov Substitution Principle) é mais um dos 5 Princípios SOLID que todo bom programador e/ou arquiteto de software deveria conhecer. No entanto requer uma atenção especial pois ele é abstrato e mais difícil de entender, principalmente para quem está começando com SOLID.

Definição do LSP

Barbara Liskov, em maio de 1988, escreveu o seguinte texto para definir subtipos:

Se, para cada objeto o1 de tipo S, houver um objeto o2 de tipo T, de modo que, para todos os programas de tipo P não seja modificado quando o1 for substituído por o2, então S é um subtipo de T.

Nazaré Confusa

Sim! O nó no cérebro acontece de forma natural quando você tem o primeiro contato com esta definição. Mas não se desespere pois o texto escrito por Liskov pode ser simplificado e entendido como:

Sempre que o programa esperar receber uma instância de um tipo T, um subtipo S derivado de T deve poder substituí-lo sem que o programa precise de qualquer adequação para atender uma instância do subtipo S. Caso contrário, S não deveria ser um subtipo derivado de T.

Importante: ⚠️

👉 O LSP não se trata apenas de uma orientação sobre o uso da herança. Com o passar dos anos, o LSP se transformou em um princípio de design de software aplicável a qualquer tipo de interface e implementação.

👉 De forma mais abrangente, o segredo do sucesso é preservar as expectativas e garantir que o comportamento dos subtipos seja compatível com a intenção do tipo base.

Fechou? Agora podemos continuar e você já pode tirar um print desta informação e fazer um belo papel de parede! 😊

Violação do LSP

Montei um exemplo em .NET C# (simples e para fins didáticos) que deve ajudar a entender uma das formas de violar o Princípio da Substituição de Liskov.

Observe com calma cada uma das classes:

public class Vehicle 
{
    public bool IsIgnitionOn { get; protected set; }

    public bool IsMoving { get; private set; }

    public virtual void StartIgnition()
    {
        this.IsIgnitionOn = true;
    }

    public void StartMove()
    { 
        this.IsMoving = true;
    }
}

public class Car : Vehicle
{
    public override void StartIgnition()
    {
        this.IsIgnitionOn = true;
    }
}

public class Motorcycle : Vehicle
{
    public override void StartIgnition()
    {
        this.IsIgnitionOn = true;
    }
}

public class Bicycle : Vehicle
{
    public override void StartIgnition()
    {
        throw new NotImplementedException();
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos ver o que acontece quando executamos o método StartIgnition para diferentes instâncias de Vehicle:

public void StartVehicleIgnition(Vehicle vehicle)
{
    vehicle.StartIgnition();

    if (vehicle.IsIgnitionOn) 
    {
        Console.WriteLine(
            $"O veículo está com a ignição ligada.");
    }
}

StartVehicleIgnition(new Vehicle()); 
// ✅ O veículo está com a ignição ligada.

StartVehicleIgnition(new Car()); 
// ✅ O veículo está com a ignição ligada.

StartVehicleIgnition(new Motorcycle()); 
// ✅ O veículo está com a ignição ligada.

StartVehicleIgnition(new Bicycle()); 
// ❌ NotImplementedException
Enter fullscreen mode Exit fullscreen mode

Neste caso fica evidente que, apesar de a bicicleta ser um veículo, no escopo deste programa, uma instância de Bicycle não consegue se adequar ao método StartIgnition e muito menos à propriedade IsIgnitionOn que representa um estado interno herdado da classe base Vehicle.

Alterar o programa, incluindo um tratamento de erro para atender única e exclusivamente uma ou mais instâncias de Bicycle, deveria ser considerado um crime.

Mas como eu sempre digo: Você não está aqui por acaso! A entropia do universo, de uma forma singular, te trouxe até aqui e agora você terá um bom exemplo para compartilhar com os seus colegas de trabalho para que eles também não cometam mais esse tipo erro.

Aplicação do LSP

Para corrigir e evitar que problemas como estes ocorram, poderiamos seguir com a seguinte abordagem:

public class Vehicle 
{
    public bool IsMoving { get; private set; }

    public void StartMove()
    {  
        this.IsMoving = true;
    }
}

public interface IVehicleMotorized 
{
    decimal IsIgnitionOn { get; }

    void StartIgnition();
}

public class Car : Vehicle, IVehicleMotorized 
{
    public decimal IsIgnitionOn { get; private set; }

    public void StartIgnition()
    {
        this.IsIgnitionOn = true;
    }
}

public class Motorcycle : Vehicle, IVehicleMotorized 
{
    public decimal IsIgnitionOn { get; private set; }

    public void StartIgnition()
    {
        this.IsIgnitionOn = true;
    }
}

public class Bicycle : Vehicle
{
    // Atributos e métodos específicos para bicicletas.
}
Enter fullscreen mode Exit fullscreen mode

Neste caso criamos uma interface denominada IVehicleMotorized e fizemos as classes Car e Motorcycle implementarem as propriedades e comportamentos da nossa nova abstração.

Quanto à classe Vehicle, removemos tudo o que não pode ser utilizado em bicicletas e deixamos apenas comportamentos/métodos que estejam diretamente relacionados com o que chamamos de veículos no escopo deste programa.

Vamos refatorar o trecho onde utilizamos o método StartIgnition e a propriedade IsIgnitionOn para garantir que apenas classes que implementem a interface IVehicleMotorized possam ser acionadas no método StartVehicleIgnition:

public void StartVehicleIgnition(IVehicleMotorized  vehicle)
{
    vehicle.StartIgnition();

    if (vehicle.IsIgnitionOn) 
    {
        Console.WriteLine(
            $"O veículo está com a ignição ligada.");
    }
}

StartVehicleIgnition(new Car()); 
// ✅ O veículo está com a ignição ligada.

StartVehicleIgnition(new Motorcycle()); 
// ✅ O veículo está com a ignição ligada.
Enter fullscreen mode Exit fullscreen mode

Para concluir, vamos criar um programa de exemplo onde nenhum comportamento inesperado acontece ao realizar a substituição da classe base Vehicle por qualquer uma das outras classes derivadas:

public void StartMoveVehicle(Vehicle vehicle)
{
    vehicle.StartMove();

    if (vehicle.IsMoving) {
        Console.WriteLine("O veículo está em movimento.");
    }
}

StartMoveVehicle(new Vehicle()); 
// ✅ O veículo está em movimento.

StartMoveVehicle(new Car()); 
// ✅ O veículo está em movimento.

StartMoveVehicle(new Motorcycle()); 
// ✅ O veículo está em movimento.

StartMoveVehicle(new Bicycle()); 
// ✅ O veículo está em movimento.
Enter fullscreen mode Exit fullscreen mode

Finalizamos por aqui! 😊

Como mencionei anteriormente, este foi um exemplo de uma dentre outras formas de violação deste princípio. Esta discussão pode ser bem extensa e acho que merece um vídeo no futuro. 👀

Antes de ir embora, me diz aí: você já cometeu ou testemunhou algum tipo de violação do LSP?


Obrigado pela sua atenção e espero que este artigo tenha sido útil para você.

Me siga para receber mais conteúdos como este. ❤️


Recomendação de leitura:
Arquitetura Limpa - O Guia do Artesão para Estrutura e Design de Software
Robert Cecil Martin, 2019.

solidprinciples Article's
30 articles in total
Favicon
ISP - O Princípio da Segregação de Interface
Favicon
Disadvantages of the Single Responsibility Principle(SRP)
Favicon
Guia: O que é SOLID
Favicon
Interface Segregation Principle (ISP)
Favicon
Dependency Inversion Principle (DIP)
Favicon
Liskov Substitution Principle (LSP)
Favicon
Create your own Logger using Open-closed principle
Favicon
SOLID: Dependency Inversion Principle (DIP) in C#
Favicon
SOLID: Principio de Abierto/Cerrado
Favicon
LSP - O Princípio da Substituição de Liskov
Favicon
SOLID: Principio de Responsabilidad Única
Favicon
SOLID Principles for React / React Native Development
Favicon
Single Responsibility Principle (SRP)
Favicon
Solid Prensipleri
Favicon
Mastering SOLID Principles in .NET Core: A Path to Clean and Scalable Code
Favicon
OCP - O Princípio Aberto/Fechado
Favicon
SRP - O Princípio da Responsabilidade Única
Favicon
SOLID Design Principles
Favicon
Rethinking interfaces in Flutter projects
Favicon
Understanding the SOLID Principles in PHP and How They Improve Code Quality
Favicon
SOLID: The Liskov Substitution Principle (LSP) in C#
Favicon
Is Clean Code really practical?
Favicon
Open/Closed Principle (OCP)
Favicon
Single Responsibility Principle (SRP)
Favicon
Solid Principle in Simple English
Favicon
Why Clean Architecture and SOLID Principles Should Be the Foundation of Your Next Project!
Favicon
Seja um desenvolvedor melhor com S.O.L.I.D.
Favicon
SOLID: Open-Closed Principle (OCP) in C#
Favicon
SOLID Principles Explained in a Simple Way 🛠️✨ with Real-Life Examples
Favicon
Object Oriented Design Balance With Understanding Anti-Single Responsibility Principle

Featured ones: