13.14. Herança e Polimorfismo em Java: Princípio de Substituição de Liskov
A programação orientada a objetos (POO) é um paradigma de programação que utiliza "objetos" para modelar dados e comportamentos. Java, sendo uma linguagem orientada a objetos, permite que os programadores aproveitem conceitos como herança e polimorfismo para criar programas mais flexíveis e reutilizáveis. Vamos aprofundar nosso entendimento sobre esses conceitos e explorar o Princípio de Substituição de Liskov (LSP), um dos princípios fundamentais da programação orientada a objetos.
Herança em Java
Herança é um mecanismo pelo qual uma nova classe, chamada subclasse, pode herdar campos e métodos de outra classe, chamada superclasse. Isso permite que subclasses reutilizem e estendam o comportamento das superclasses. Em Java, a herança é realizada com a palavra-chave extends
.
class Veiculo {
public void mover() {
System.out.println("Veículo em movimento");
}
}
class Carro extends Veiculo {
@Override
public void mover() {
System.out.println("Carro em movimento");
}
}
No exemplo acima, Carro
é uma subclasse de Veiculo
. A subclasse Carro
herda o método mover()
e fornece sua própria implementação usando a anotação @Override
. Isso ilustra a extensão e a reutilização de funcionalidades.
Polimorfismo em Java
Polimorfismo é a capacidade de um objeto ser tratado como uma instância de sua própria classe ou de qualquer classe da qual ele herda. Em Java, isso é comumente realizado através da sobrescrita de métodos (method overriding) e da sobrecarga de métodos (method overloading).
Com a sobrescrita de métodos, uma subclasse pode fornecer uma implementação específica de um método que já existe em sua superclasse. Já a sobrecarga de métodos permite que vários métodos tenham o mesmo nome, mas com diferentes listas de parâmetros.
Princípio de Substituição de Liskov (LSP)
O Princípio de Substituição de Liskov, formulado por Barbara Liskov em 1987, é um conceito fundamental na programação orientada a objetos. O LSP afirma que se S
é um subtipo de T
, então objetos do tipo T
em um programa podem ser substituídos por objetos do tipo S
sem alterar as propriedades desejáveis desse programa. Em outras palavras, uma subclasse deve ser substituível por sua superclasse sem causar erros.
Para aderir ao LSP, uma subclasse deve:
- Não alterar o comportamento esperado dos métodos da superclasse.
- Não violar as invariáveis e contratos estabelecidos pela superclasse.
- Garantir que os subtipos possam ser usados em substituição aos tipos base sem que haja necessidade de conhecer a diferença entre eles.
Violar o LSP pode levar a bugs e comportamentos inesperados em um programa. Por exemplo:
class Passaro {
public void voar() {
System.out.println("Pássaro voando");
}
}
class Pinguim extends Passaro {
@Override
public void voar() {
throw new UnsupportedOperationException("Pinguins não voam");
}
}
No exemplo acima, Pinguim
é uma subclasse de Passaro
, mas sobrescreve o método voar()
para lançar uma exceção, pois pinguins não voam. Isso viola o LSP, pois o comportamento da superclasse Passaro
é alterado de uma maneira que pode causar erros se objetos de Pinguim
forem usados em contextos que esperam que todos os pássaros possam voar.
Como Aplicar o LSP em Java
Para garantir que o LSP seja respeitado em Java, siga estas diretrizes:
- Evite sobrescrever métodos de uma forma que mude o comportamento esperado.
- Use a anotação
@Override
ao sobrescrever métodos para garantir que você está de fato modificando um método da superclasse. - Quando necessário, use composição em vez de herança para compartilhar comportamentos entre classes que não têm uma relação de substituibilidade.
- Respeite os contratos estabelecidos pela superclasse, incluindo pré-condições, pós-condições e invariantes.
Adotar o LSP ajuda a manter a integridade do design orientado a objetos, promovendo a criação de sistemas mais robustos e manuteníveis.
Conclusão
Herança e polimorfismo são conceitos poderosos em Java que, quando usados corretamente, podem aumentar a reutilização de código e flexibilidade. O Princípio de Substituição de Liskov é essencial para usar esses conceitos de forma eficaz, garantindo que as subclasses possam ser usadas em lugar de suas superclasses sem causar efeitos colaterais indesejados. Ao projetar e implementar suas classes em Java, mantenha sempre o LSP em mente para criar um código mais confiável e fácil de manter.