🖥️
Padrões de Projeto
  • Padrões de Projeto
  • Orientação a Objetos
    • Conceitos básicos de orientação a objetos
      • Objetos e classes
      • Encapsulamento
      • Relacionamentos
      • Herança
      • Realização
      • Polimorfismo
      • Imutabilidade
  • Princípios SOLID
    • Introdução
    • SRP - Princípio de Responsabilidade Única
    • OCP - Princípio de Aberto/Fechado
    • LSP - Princípio de Substituição de Liskov
    • ISP - Princípio de Segregação de Interfaces
    • DIP - Princípio de Inversão de Dependência
  • Padrões de Projetos e catálogos
    • Introdução
  • Padrões Comportamentais
    • Padrão Strategy
    • Padrão State
    • Padrão Observer
    • Padrão Chain of Responsibility
    • Padrão Command
    • Padrão Template Method
    • Padrão Null Object
  • Padrões Criacionais
    • Padrão Singleton
    • Padrão Prototype
    • Padrão Builder
    • Padrões Factory
      • Factory Method
      • Abstract Factory
  • Padrões Estruturais
  • Padrão Adapter
  • Padrão Facade
  • Padrão Decorator
  • Padrão Proxy
  • Padrão Bridge
  • Padrão Composite
Powered by GitBook
On this page

Was this helpful?

  1. Princípios SOLID

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

PreviousOCP - Princípio de Aberto/FechadoNextISP - Princípio de Segregação de Interfaces

Last updated 4 years ago

Was this helpful?

introduziu o Princípio de substituição de Liskov como uma definição particular para o conceito de subtipo. O princípio foi definido de como:

Seé uma propriedade demonstrável dos objetos de tipo . Então deve ser verdadeiro para de tipo onde é um subtipo .

Falando de outra maneira, a visão de "subtipo" defendida por Liskov é baseada na noção da substituição; isto é, se S é um subtipo de T, então os objetos do tipo T, em um programa, podem ser substituídos pelos objetos de tipo S sem que seja necessário alterar as propriedades deste programa.

Suponha que uma parte do sistema está utilizando uma determinada funcionalidade. Se essa funcionalidade precisar ser trocada por outra, por meio de polimorfismo dinâmico ou estático, a outra deverá devolver o mesmo tipo de informação, caso contrário, o sistema quebrará.

Neste contexto, para garantir que a classe S tenha o mesmo comportamento que a classe base T, é imprescindível a utilização de um contrato (interface ou classe abstrata), contendo as definições de métodos necessárias para que as classes que a herdarem sejam obrigadas a implementá-las.

Um exemplo clássico para demonstrar uma violação desse princípio é a modelagem de classes Quadrado e Retangulo. Todo quadrado é um retângulo? Sim, por definição. Se é um, portanto é lógico e intuitivo modelar uma classe Quadrado como sendo derivada da classe Retangulo. Aqui provavelmente conseguiriamos utilizar herança para modelar essas classes. Então teriamos as duas classes da seguinte maneira

public class Retangulo {
    
    private double altura;
    private double largura;

    public Retangulo(double altura, double largura) {
        this.altura = altura;
        this.largura = largura;
    }

    public double getAltura() {
        return altura;
    }

    public void setAltura(double altura) {
        this.altura = altura;
    }

    public double getLargura() {
        return largura;
    }

    public void setLargura(double largura) {
        this.largura = largura;
    }
}

E a classe Quadrado. Perceba que sobrescrevemos os métodos set para garantir que ele continua sendo um quadrado, ou seja, tenho os lados iguais.

public class Quadrado extends Retangulo {

    public Quadrado(double altura, double largura) {
        super(altura, largura);
    }

    @Override
    public void setAltura(double altura) {
        super.setAltura(altura);
        super.setLargura(altura);
    }

    @Override
    public void setLargura(double largura) {
        super.setAltura(largura);
        super.setLargura(largura);
    }
}

Com isso, a classe derivada viola uma regra estabelecida na classe base: a de que altura e comprimento variam independentemente. E quando isso vai se mostrar um problema? Vejamos o exemplo a seguir

public void metodoXPTO(Retangulo retangulo) {
   retangulo.setAltura(retangulo.getAltura() * 2);
   retangulo.setComprimento(retangulo.getComprimento * 4);
   // Realiza alguma operação com os novos dados
}

O desenvolvedor facilmente pode assumir que estava lidando com um Retangulo (afinal, é o tipo do parâmetro do método) e aplicou um cálculo que variasse suas dimensões. No entanto, caso o método receba, em tempo de execução, um objeto do tipo Quadrado teremos um comportamento inesperado: o cálculo para redimensionar o retângulo o transformará em um quadrado, isto é, ao quadruplicar o comprimento, inadvertidamente, a altura também foi quadruplicada!

O problema acima só existe porque, como dito anteriormente, a classe derivada não respeita a regra da classe base de variar os lados de forma independente. Sendo assim, do ponto de vista computacional, Quadrado não é um Retangulo, pois ambos possuem comportamentos diferentes em relação a alteração de seus lados.

Esse cenário de quebra do princípio ocorre muitas vezes pela nossa intuição de que Herança sempre ocorre quando temos um cenário de "é um", quando na verdade isso não é verdadeiro. Essa idéia ajuda na maioria das oportunidades de Herança, mas é sempre bom ter em mente o Princípio de Substituição de Liskov para verificarmos se realmente faz sentido.

Barbara Liskov
forma resumida
x
T
S
q(x)
y
T
S
{\displaystyle q(y)}