Quando perguntaram a James Gosling, o criador do Java, o que
ele faria diferente se tivesse que recriar o Java, ele respondeu que já tinha
pensado em como seria uma ter uma linguagem que só haveria delegação (VENNERS,
2001). Ele estava querendo dizer que estava tentando imaginar uma linguagem
orientada a objetos que não implementasse o uso de herança. Porquê? Sabemos que
a herança é um dos quatro pilares da Orientação a Objetos, sendo que uma
linguagem de programação, não é verdadeiramente considerada Orientada a
Objetos, se não possuir sua implementação de herança (veja anexo no
final deste artigo).
A herança é um mecanismo para expressar a similaridade entre
classes, simplificando a definição de classes similares a outras que já foram
definidas. Ela representa generalização e especialização, tornando atributos e
serviços comuns em uma hierarquia de classes. Define uma subclasse como: É-UM;
É UM TIPO DE(uma pessoa jurídica é uma pessoa).
Já a delegação é um modo mais geral de estender uma classe,
onde um objeto, em vez de realizar uma de suas tarefas, delega tal tarefa a um
objeto auxiliar associado. A estratégia é escrever uma classe adicional para
prover a funcionalidade desejada e manter uma referência desta classe na classe
original. Ela expressa, portanto, composição ou agregação de classes como:
TEM-UM; É COMPOSTO POR (um pedido é composto por um ou mais itens).
A herança permite um projeto elegante, substituindo grande
quantidade de atributos e métodos duplicados em classes semelhantes, tornando a
reutilização de código muito simples, apenas por herança, onde cada nível de
hierarquia de classes reutiliza os códigos dos níveis superiores.
Refatoramos as classes gerando um novo modelo:
Qual o problema
com herança, então? Bom, na verdade há vantagens importantes e
desvantagens perigosas. Em geral usar composição traz mais vantagens do que
utilizar herança. Através da herança, novas classes podem ser derivadas de
classes existentes, economizando muita escrita de código. O problema é que, ao
mesmo tempo, a herança fornece um caminho fácil demais para subverter outros
princípios da orientação a objetos. Por exemplo: Se um gato possui raça e patas, e um cachorro possui raça, patas e tipoDoPelo, logo Cachorro
extends (herda de) Gato?
Pode parecer engraçado mas é muito comum. É a herança por preguiça, por
comodismo, e facilmente caímos nessa armadilha. A relação “é um” não se
encaixa neste contexto e vai gerar problemas se utilizada.
Paulo Silveira
nos dá mais detalhes:
Em vários itens do livro Effective Java, Joshua
Bloch cita o uso de herança e de membros package-default e protected.
O item 12 diz “minimize o acesso a suas classes e membros“, o item 15
diz “desenhe suas classes pensando no uso de herança, caso contrário
proíba-a” e o item 16 “prefira interfaces a classes abstratas“. Mas
sem dúvida o principal é o item 14: “prefira composição em vez de herança“,
onde Joshua Bloch diz com todas as letras que herança quebra o
encapsulamento. E isso não é novidade, essa
conclusão é atribuída a Alan Snyder, no artigo entitulado Encapsulation and Inheritance in
Object-Oriented Programming Languages, que data de 1986! Faz incríveis 20
anos que alguém percebeu que “… na maioria das linguagens a introdução da
herança compromete seriamente os benefícios do encapsulamento …” (traduzido
livremente do abstract do artigo citado). Se você preferir uma opinião mais
atual, pode ler esse
post do Martin Fowler, que tem menos
de um mês.No livro, Joshua Bloch dá como exemplo a criação de uma classe filha
de HashSet, e mostra que, sem conhecer profundamente o código fonte da classe
mãe, fica impossível de que essa classe filha funcione corretamente ao
reescrever um método que é aparentemente inofensivo. A partir do momento que
você precisa conhecer o código fonte da sua mãe, você quebrou o encapsulamento.
Mais ainda: quando a classe mãe precisar sofrer alguma modificação, o
desenvolvedor precisa estar ciente que pode quebrar o funcionamento de várias
classes filhas, que pressupunham determinado comportamento interno. Leitura
recomendadíssima. (SILVEIRA, 2006).
Peter Coad nos
dá cinco regras para o uso de herança:
· O
objeto "é um tipo especial de" e não "um papel assumido
por";
· O
objeto nunca tem que mudar para outra classe;
· A
subclasse estende a superclasse mas não faz override ou anulação de variáveis
e/ou métodos;
·
Não é uma subclasse de uma classe "utilitária";
·
Para classes do domínio do problema, a subclasse expressa tipos
especiais de papeis, transações ou dispositivos;
Resumindo
temos, como benefícios e problemas da herança os seguintes pontos:
·
Benefícios da herança:
·
Captura o que é comum e o isola daquilo que é diferente;
·
A herança é vista diretamente no código.
·
Problemas da herança:
·
O encapsulamento entre classes e subclasses é fraco (o acoplamento é
forte);
o
Mudar uma superclasse pode afetar todas as subclasses;
o
Isso viola um dos princípios básicos de projeto OO (manter fraco
acoplamento).
·
Às vezes um objeto precisa ser de uma classe diferente em momentos
diferentes.
o Com
herança, a estrutura está parafusada no código e não pode sofrer alterações em
tempo de execução.
o
A herança é um relacionamento estático que não muda com tempo.
Erich Gamma,
Richard Helm, Ralph Johnson, John Vlissides, a guangue dos
quatros (Guangue of Four, ou GoF) descreveram uma série de
padrões de projeto e desenvolvimento de software, baseado em experiência, que
podem nos ajudar a decidir quando utilizar a herança e quando não.
“Separar partes mutáveis das partes que não mudam” e “composição é preferível a herança” são dois princípios de projeto comuns quando você inicia no mundo da Programação Orientada à Objetos. Entretanto, enquanto o primeiro parece lógico, alguém pode pensar porque é preferível usar composição em vez de herança, e é uma questão lógica, então vamos respondê-la com um exemplo.
Repare que sempre podemos substituir herança através de
um refatoramento simples: extração de uma interface com os métodos herdados,
somado a criação de uma implementação que simplesmente delega esses métodos
para a antiga classe mãe. Quando então usar herança? Essa é uma questão
difícil. Na minha visão particular, a resposta seria um enfático “quase nunca”.
Creio que a resposta aqui fique um pouco a critério de cada desenvolvedor, mas
sempre com muita cautela! (SILVEIRA, 2006).
Exemplo Prático
Vamos ilustrar o
uso de herança versus delegação utilizando o seguinte cenário: pessoas
envolvidas na aviação:
Uma pessoa, aqui, pode mudar de papel ou assumir uma combinação de papéis. Por
exemplo, uma pessoa pode, num momento fazer parte da tripulação e, em outro,
ser apenas um passageiro. Para permitir papéis múltiplos através de
herança seriam necessárias sete combinações, ou subclasses.
O que é delegação?
Para solucionar
esse problema usamos a composição, ou seja, estendemos as responsabilidades
pela delegação de trabalho a outros objetos.
Delegação é o
processo de delegar funcionalidade às partes contidas.
Note que as
chamadas get/setNome e get/setEndereço das classes Tripulação, Passageiro e
Agente, apenas chamam os métodos correspondentes da classe Pessoa. Isso é
delegação: repassar funcionalidades às partes contidas.
Delegação vem
junto com composição para oferecer soluções flexíveis e elegantes como essa,
respeitando o princípio de “separar código mutável de código estático”.
Contudo, temos que pagar um preço por isso: a necessidade de métodos que
“empacotem” as chamadas impõem um tempo extra de processamento por causa das
chamadas a esses métodos.
No exemplo
anterior, usando composição:
- Estamos
estendendo a funcionalidade de Pessoa de várias formas, mas sem usar
herança
- Observe
que também podemos inverter a composição (uma pessoa tem um ou mais
papeis)
- Aqui,
estamos usando delegação: dois objetos estão envolvidos em atender um
pedido (digamos setNome)
·
O objeto tripulação, por exemplo, delega setNome para o objeto pessoa
que ele tem por composição; técnica também chamada de forwarding;
·
É semelhante a uma subclasse delegar uma operação para a superclasse
(herdando a operação);
·
Delegação sempre pode ser usada para substituir a herança;
·
Se usássemos herança, o objeto tripulação poderia referenciar a pessoa
com this;
·
Com o uso de delegação, tripulação pode passar this para Pessoa e o
objeto Pessoa pode referenciar o objeto original se quiser;
·
Em vez de tripulação ser uma pessoa, ele tem uma pessoa;
·
A grande vantagem da delegação é que o comportamento pode ser escolhido
em tempo de execução (late biding) e vez de estar amarrado em tempo de
compilação (early biding);
·
A grande desvantagem é que um software muito dinâmico e parametrizado é
mais difícil de entender do que software mais estático;
Podemos
acrescentar:
- Os
objetos que foram instanciados e estão contidos na classe que os
instanciou são acessados somente através de sua interface;
- A
composição pode ser definida dinamicamente em tempo de execução pela
obtenção de referência de objetos a objetos de do mesmo tipo;
- A
composição apresenta uma menor dependência de implementações;
- Na
composição temos cada classe focada em apenas uma tarefa (princípio SRP - Single
Responsability Principle ou Princípio da Responsabilidade Única também
conhecido por Coesão < http://www.macoratti.net/11/05/pa_solid.htm>;
- Na
composição temos um bom encapsulamento visto que os detalhes internos dos
objetos instanciados não são visíveis
Bibliografia
ORIENTAÇÃO A OBJETOS. In: WIKIPÉDIA, a enciclopédia livre. Flórida:
Wikimedia Foundation, 2013. Disponível em:
<http://pt.wikipedia.org/w/index.php?title=Orienta%C3%A7%C3%A3o_a_objetos&oldid=35146444>.
Acesso em: 1 abr. 2013.
KAUPA, Paulo. Os 4 pilares da Programação Orientada a
Objetos. Disponível em: <http://www.devmedia.com.br/os-4-pilares-da-programacao-orientada-a-objetos/9264
>. Acesso em: 1 abr. 2013.
SILVEIRA, Paulo. Como não aprender orientação a objetos: Herança.
Caelum, 2006. Disponível em: < http://blog.caelum.com.br/como-nao-aprender-orientacao-a-objetos-heranca/>
. Acesso em: 1 abr. 2013.
VENNERS,
Bill. A conversation with James Gosling. The inventor of Java discusses the
current state of software. JavaWorld.com, 06 abr 2001. Disponível em:
<
http://www.javaworld.com/javaworld/jw-06-2001/j1-01-gosling.html?page=1>.
Acesso em: 1 abr. 2013.
Anexo
Os
Quatro Pilares da Orientação a Objetos
· Herança (ou
generalização) é o mecanismo pelo qual uma classe (sub-classe) pode estender
outra classe (super-classe), aproveitando seus comportamentos (métodos) e variáveis
possíveis (atributos). Um exemplo de herança: Mamífero é super-classe de
Humano. Ou seja, um Humano é um mamífero. Há herança múltipla quando uma
sub-classe possui mais de uma super-classe. Essa relação é normalmente chamada
de relação "é um";
· Encapsulamento consiste na
separação de aspectos internos e externos de um objeto. Este mecanismo é
utilizado amplamente para impedir o acesso direto ao estado de um objeto (seus
atributos), disponibilizando externamente apenas os métodos que alteram estes
estados. Exemplo: você não precisa conhecer os detalhes dos circuitos de um
telefone para utilizá-lo. A carcaça do telefone encapsula esses detalhes,
provendo a você uma interface mais amigável (os botões, o monofone e os sinais
de tom);
· Abstração é a habilidade
de concentrar nos aspectos essenciais de um contexto qualquer, ignorando
características menos importantes ou acidentais. Em modelagem orientada a
objetos, uma classe é uma abstração de entidades existentes no domínio do
sistema de software;
· Polimorfismo consiste em
quatro propriedades que a linguagem pode ter (atente para o fato de que nem
toda linguagem orientada a objeto tem implementado todos os tipos de
polimorfismo):
· Universal:
o Inclusão: um ponteiro
para classe mãe pode apontar para uma instância de uma classe filha (exemplo em
Java: "List lista = new LinkedList();" (tipo de polimorfismo mais
básico que existe)
o Paramétrico: se restringe
ao uso de templates (C++, por exemplo) e generics (Java/C♯)
· Ad-Hoc:
o Sobrecarga: duas
funções/métodos com o mesmo nome mas assinaturas diferentes
o Coerção: a linguagem
que faz as conversões implicitamente (como por exemplo atribuir um int a um
float em C++, isto é aceito mesmo sendo tipos diferentes pois a conversão é
feita implicitamente)