A herança é um dos primeiros conceitos que aprendemos ao programar orientado a objetos. A ideia de criar classes derivadas que herdam funcionalidades de uma classe pai parece uma solução elegante para reutilizar código. No entanto, à medida que um projeto cresce e evolui, os problemas da herança começam a aparecer.
Neste artigo, discutiremos por que a composição é uma abordagem mais flexível e sustentável para reutilização de código, abordando suas vantagens e como utilizá-la corretamente.
Mas o que é Herança?
A herança permite que uma classe (subclasse) herde funcionalidades de outra classe (superclasse). Isso cria uma hierarquia onde a subclasse pode reutilizar ou modificar comportamentos definidos na superclasse.
A herança tem duas capacidades: a capacidade de reutilizar o código e a capacidade de construir uma abstração. Ela permite que um consumidor pense que está recebendo uma superclasse, mas na verdade está recebendo uma subclasse.
Os problemas com Herança
A herança pode parecer uma solução conveniente, mas impõe desafios significativos:
1. Acoplamento Excessivo
A herança cria um forte acoplamento entre a superclasse e suas subclasses. Mudanças na superclasse podem quebrar as subclasses, mesmo que estas não dependam diretamente das modificações.
2. Fragilidade do Código
Como as subclasses dependem da superclasse, alterações podem introduzir bugs inesperados e difíceis de rastrear.
3. Rigidez na Evolução do Código
Uma estrutura hierárquica pode se tornar inflexível com o tempo, dificultando alterações e extensões.
Os problemas da herança aparecem quando você precisa modificar o código. A mudança é o inimigo do design perfeito, e você muitas vezes se prende a uma estrutura difícil de alterar
Imaginando um exemplo
Vamos olhar um exemplo utilizando a linguagem de programação PHP:
class Veiculo {
public function buzinar() {
return "Beep! Beep!";
}
public function empinar() {
return "Empinando!";
}
}
class Carro extends Veiculo {
public function buzinar() {
return "Fon! Fon!";
}
public function empinar() {
throw new Exception("Carro não empina!");
}
}
Aqui temos um exemplo de funcionalidade herdada pela superclasse no qual uma subclasse não precisa implementar. Ao longo da vida do software, basta que regras ou necessidades mudem para que problemas como esse se repitam ao longo de todo o código.
E o que é Composição?
A composição envolve a criação de objetos que contêm instâncias de outras classes, permitindo que elas trabalhem juntas sem a necessidade de uma relação hierárquica.
A composição reduz a superfície de contato entre objetos, o que resulta em menos atrito conforme surgem mudanças
Na composição, você não precisa agrupar todos os elementos comuns em uma superclasse e não precisa alterar nenhuma das outras classes para adicionar novas classes.
Com a composição, temos:
1. Maior Flexibilidade
A composição permite combinar classes de maneira mais modular e flexível, adaptando o código às necessidades específicas.
2. Menos Acoplamento
Os objetos são independentes e podem ser modificados ou substituídos sem impactar outras partes do sistema.
3. Reutilização Granular
Apenas as partes necessárias do código são reutilizadas, evitando herdar funcionalidades desnecessárias.
Exemplo de Composição
class Veiculo {
public function buzinar() {
return "Beep! Beep!";
}
public function empinar() {
return "Empinando!";
}
}
class Carro {
{
private $veiculo;
public function __construct(Veiculo $veiculo = null) {
$this->veiculo = $veiculo;
}
public function buzinar() {
return $this->veiculo->buzinar();
}
}
Dessa forma, apesar do método empinar()
estar disponível, nós não precisamos nos preocupar com ele. E o melhor de tudo, se o método empinar()
precisar ser modificado, ele não vai afetar a nossa classe Carro
.
Interfaces
Você já deve ter ouvido falar de Interfaces, certo? As interfaces vão ter um papel fundamental na flexibilidade de compartilhamento de código.
As interfaces definem um contrato, especificando os métodos que uma classe deve implementar. Isso permite a criação de abstrações sem os problemas da herança.
Vamos ver como fica o nosso exemplo adicionando interfaces:
interface VeiculoInterface {
public function buzinar();
}
class Carro implements VeiculoInterface {
public function buzinar() {
return "Fon! Fon!";
}
}
class Moto implements VeiculoInterface {
public function buzinar() {
return "Bip! Bip!";
}
}
Veja que, agora, as classes que assinam o contrato com a interface precisam implementar os métodos daquela interface. Toda classe que implementar VeiculoInterface
pode ser utilizada sem precisar herdar funcionalidades desnecessárias.
A utilização ficaria mais ou menos assim:
public class testarVeiculo(VeiculoInterface $veiculo){
return $veiculo->buzinar();
}
$carro = new Carro();
$moto = new Moto();
testarVeiculo($carro);
testarVeiculo($moto);
Interfaces e Injeção de Dependência
No último exemplo, já adentramos uma outra técnica que anda de mãos dadas com interfaces, que é a Injeção de Dependência.
Passar uma interface como dependência é a essência da injeção de dependência.
A injeção de dependência é um padrão que fornece dependências externamente em vez de criá-las internamente. Isso torna o código mais testável, modular e flexível.
class TestadorDeVeiculos {
private VeiculoInterface $veiculo;
public function __construct(VeiculoInterface $veiculo){
$this->veiculo = $veiculo;
}
public function testar(){
$this->veiculo->buzinar();
}
}
Agora, o TestadorDeVeiculos poderá testar qualquer classe que implemente o VeiculoInterface
. Podemos criar caminhões, sedans, carrinho de golfe, etc. E o melhor de tudo? Não vamos precisar mexer em nada que não seja a nova classe que estamos implementando, poderoso não?
Considerações Finais
O uso da herança pode ser tentador, mas na maioria dos casos, a composição é uma escolha mais inteligente. Embora possa gerar um pouco mais de código inicial, ela oferece mais flexibilidade, reduz o acoplamento e facilita a manutenção do sistema.
É importante não demonizar a herança, ela ainda tem seu lugar em casos específicos, como sistemas legados com código altamente repetitivo, mas para projetos modernos, a composição é a chave para um código mais limpo e sustentável.
Que tal revisar seu projeto e ver onde a composição pode substituir a herança?
Top comments (0)