No artigo anterior falei sobre Violação de Encapsulamento. Nesse artigo irei falar sobre a Duplicação de Código ao utilizarmos a Herança de Classe.
Clique aqui para ler a Parte #2 dessa série, caso ainda não tenha lido.
Introdução
Duplicação de Código utilizando Herança. Essa afirmação pode parecer heresia porque a maioria dos programadores utilizam Herança de Classe justamente para não duplicar o código.
Em uma hierarquia de classes onde B
e C
herdam de A
, basta adicionar a nova funcionalidade em A
para termos
disponível em B
e C
. Simples, rápido e sem duplicar código.
Mas será esse o melhor design para reaproveitar e não duplicar código?
A Herança de Classe é simples de usar e entender, mas no longo prazo é provado que essa não é a melhor escolha ao projetar seu diagrama de Classes. Ao invés de Herança a melhor escolha é a Composição de Objetos.
Quando estamos começando um novo projeto — ou um novo módulo — sim, essa parece ser ser a “melhor maneira” para reaproveitar código.
Parece.
Com certeza a Herança é simples e intuitiva no entanto não é o melhor design no médio e longo prazo.
Herança de Classe leva a um código altamente acoplado e sem flexibilidade.
Duplicando o código
Então, como é possível duplicar o código utilizando Herança?
Utilizando Hierarquias de Classes.
É contra intuitivo pensar nisso, mas é verdade. Pense por um minuto. Se você tem um comportamento/funcionalidade espalhado entre Classes e Subclasses dentro de uma Hierarquia, como reutilizar esse código em outras Classes ou Hierarquias diferentes? Difícil.
Não estou dizendo que é impossível. Estou dizendo que dificulta muito a reutilização devido o comportamento não ter sido codificado de maneira coesa, utilizando Classes pequenas com apenas uma responsabilidade.
Quando utilizamos Herança somos seduzidos pelo fácil reaproveitamento do código e por isso vamos agregando mais e mais funcionalidades nas Classes e Subclasses, tornando-as “inchadas” e dificultando o reaproveitamento entre módulos ou Projetos distintos.
Na maioria das vezes exigirá uma refatoração massiva no código para haver reutilização, devido as Classes e Subclasses estarem muito acopladas umas as outras.
Esse é o motivo da Duplicação de Código. É muito mais fácil e confortável — para a maioria dos programadores com prazos curtos e chefes estressados — duplicar o código do que refatorar, talvez, toda a Hierarquia.
Exemplo
Vejamos agora um exemplo utilizando Diagramas de Classes.
Antes eu gostaria de lembrá-lo que é fácil olhar um exemplo simples e pensar em inúmeras maneiras de fazê-lo melhor. Mas pense que esses são apenas exemplos para lhe mostrar o conceito e não a melhor maneira de implementar uma solução.
O Mundo Animal
Vou começar com o bom e velho “Exemplo de Animais”. O exemplo é antigo, sim, porém você ainda tem dúvidas sobre utilizar ou não herança — senão não estaria lendo esse artigo — então acho que o exemplo continua sendo válido.
Precisamos implementar um sistema que lida com Animais e resolvemos utilizar Herança de Classes para implementar uma Hierarquia e “reutilizar o código”.
Primeiramente o sistema só irá trabalhar com Cachorros e Gatos então temos o primeiro modelo:
No diagrama acima temos 3 Classes simples: Animal
, Cachorro
e Gato
.
Apenas a Classe Animal
tem um método Caminhar()
que é herdado para as Classes Cachorro
e Gato
. Queremos
reaproveitar o código então basta implementar na Classe mais acima da Hierarquia, ou seja, Animal
.
Então vamos imaginar como seria a implementação do método Caminhar()
.
function TAnimal.Caminhar: TAnimal;
begin
Result := Self;
MovePata1;
MovePata2;
MovePata3;
MovePata4;
end;
Como estamos trabalhando, a princípio, com tetrápodes ou quadrúpedes, precisamos mover as 4 patas para caminhar. Tudo funciona perfeitamente.
Então precisamos acrescentar mais alguns animais: Pato, Tubarão, Sapo, Morcego e Crocodilo.
Como somos espertos, vamos acrescentar mais abstrações para deixar o modelo mais “legal” e “sofisticado”.
Um Pato
também caminha, no entanto ele caminha utilizando 2 membros. O Sapo
e Crocodilo
caminham com 4 patas, assim
como o Cachorro
e Gato
. O Morcego
tem pernas, mas ele realmente caminha? Não sei. O Tubarão
… complicou.
Veja que pelo simples fato de termos acrescentado outros animais, toda a hierarquia deverá ser verificada afim de saber se os métodos de Classes ancestrais continuam fazendo sentido para a nova hierarquia.
O que fazer? Há inúmeras possibilidades. Algumas mais certas, outras mais erradas. Mas o que é que a maioria dos programadores fazem — inclusive em grandes empresas — quando a hierarquia não reflete o mundo real?
Eles sobrescrevem métodos, ignorando-os (métodos em branco) ou lançando uma exceção (métodos que existem, mas não devem ser utilizados… WTF!).
Nesse ponto emerge a Duplicação de Código.
Como assim?
Dependendo da quantidade de Classes numa hierarquia, duplicar a implementação será o método escolhido.
No caso do nosso exemplo o método Caminhar()
seria modificado para utilizar apenas
2 patas em algumas Classes. Em outras levantaríamos uma exceção naqueles Animais que não deveriam caminhar, como no caso da Classe Tubarão
.
Mas isso é uma solução alternativa que está longe de ser a ideal.
Solução
A Hierarquia está errada? Qual seria a implementação “certa”? Como reaproveitar o código sem usar Herança?
Quando os cientistas separam os animais em “Classes”, eles fizeram-na baseado no que eles são (identidade) e não no que eles fazem (comportamento).
Um pato nada, assim como um crocodilo. Mas o ato de nadar não pertence ao pato nem ao crocodilo, pois nadar é um comportamento “implementado” em vários outros Animais (Classes).
Vou lhe contar como resolver o problema da implementação da Hierarquia Animal.
Atenção. Aqui está:
Um Animal não caminha. Quem caminha é um Cachorro, Gato ou Pato. Animal é uma abstração, uma classificação, assim como Mamífero ou Anfíbio. Animal não existe.
Tetrápodes, Bípedes, Mamíferos, Anfíbios, Aves, Peixes, Pássaros… eles não existem!
Que?!
É isso aí.
Não entendeu? Aqui vai outro exemplo.
Você já viu uma Árvore? Tenho certeza que não! Você viu, talvez, uma goiabeira, bananeira, macieira… Árvore é uma abstração, uma classificação. Árvore não existe.
Se Mamíferos, Anfíbios, Aves, Peixes…e Árvores não existem, o que são?
São apenas Interfaces que definem uma classificação para algum comportamento.
Então não há — ou não deveria haver — Hierarquia de Classes, apenas Hierarquias de Interfaces!
Cientistas não tem problemas em classificar novas espécieis porque a inclusão na hierarquia existente não “quebra” nenhuma “funcionalidade” nas “Classes” abaixo desta. São apenas Interfaces.
Então, se não há Hierarquia de Classes, como reaproveitar código sem duplicar?
Resposta: Composição de Objetos.
Objetos tem comportamento, Interfaces não. Um cachorro é um Objeto; um pato é um Objeto; o ato de caminhar é um comportamento de “algum objeto” que os pássaros sabem utilizar… ou será que Alguém “Duplicou o Código” em cada DNA porque era mais fácil de implementar? :)
No próximo artigo…
No próximo artigo irei falar sobre Forte Acoplamento entre Classes que utilizam Herança.
Clique aqui para ler a Parte #4 dessa série.
Caso você tenha alguma dúvida ou quiser compartilhar seus pensamentos sobre essa série, utilize a área abaixo para comentários.
Até logo.