Monday, April 1, 2013

Herança e Delegação


Herança e Delegação

 

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.












Quando tivermos essa situação:


 
            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)

 

 

Search This Blog

About Me

My photo
Cristão, apaixonado por Deus. Consultor em Tecnologia da Informação