S.O.L.I.D.: Princípio de Substituição de Liskov

Artigos - 06/Fev/2020 - por André Benjamim

Introdução

Este é o terceiro artigo de uma série em que detalhamos cada um dos princípios do SOLID – um conjunto de princípios que orienta o desenvolvimento de código compreensível e fácil de manter. No artigos anteriores, discorremos sobre o Princípio da Responsabilidade Única e o Princípio Aberto/Fechado. Aqui falaremos sobre o Princípio de Substituição de Liskov (Liskov Substitution Principle).

Liskov Substitution Principle (LSP)

O Princípio de Substituição de Liskov diz que objetos podem ser substituídos por seus subtipos sem que isso afete a execução correta do programa.

Exemplo

class EmailTemplate
  attr_reader :recipient

  def initialize(recipient)
    @recipient = recipient
  end

  def headers
    # code for headers
  end

  def body
    # subclasses should implement this method. It should return a string
    raise NotImplementedError
  end

  def signature
    # code for signature
  end
end

class WelcomeEmail < EmailTemplate
  def body
    # welcome body code. String content
  end
end

class InviteEmail < EmailTemplate
  def body
    {
      h1: 'You received an invitation',
      p: {
        text: 'To start your registration click on the link',
        a: '/invitation'
      }
    }
  end

  def signature
    raise NotImplementedError
  end
end

Nesse exemplo, EmailTemplate é a classe base, indicando que cada classe que herdá-la deve implementar o método body. A classe WelcomeEmail faz a implementação desse método, enquanto InviteEmail implementa body e sobrescreve signature.

Agora, supondo que uma classe receba um objeto de uma das classes de Email (EmailTemplate, WelcomeEmail, InviteEmail) e faça chamadas dos três métodos: headers, body e signature. Qualquer subclasse de EmailTemplate deveria ser utilizada sem afetar o funcionamento correto da aplicação. Porém, não é o caso de InviteEmail, uma vez que ele lança uma exceção NotImplementedError e o body retorna um hash.

Subclasses devem manter o comportamento similar ao da classe base. Quando sobrescrevemos o método body, para retornar um hash ao invés de string, estamos quebrando o comportamento esperado. Assim, classes que esperam que o método body retorne uma string, quebram.

O mesmo problema ocorre com o método signature. Apesar da classe base possuir signature, a classe InviteEmail não implementa mais esse comportamento.

Quando esse cenário ocorrer, talvez seja melhor revisar a abstração e reavaliar o que é comportamento que deve ser herdado e o que é extensão (funcionalidade) para as classes.

Considerações Finais

O LSP tem como objetivo manter o funcionamento do código íntegro no processo de acoplamento de funcionalidades na aplicação. Esse princípio é quebrado em situações nas quais uma subclasse deixa de herdar um comportamento da classe pai, seja sobrescrevendo um método e lançando uma exceção ou não tirando proveito de todas as funcionalidades dela. Chamamos esse cenário de Refused Bequest.

Se você estiver interessado em ler mais sobre os princípios de S.O.L.I.D., recomendamos os demais artigos dessa série:

Referências

Videos

Livros e Artigos

Foto de perfil do autor
André Benjamim

Dev na Rebase

Desenvolvedor Ruby formado em Ciência da Computação. Gosta de falar de Design de Código, Arquitetura e reclamar do calor