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)

 

 

Sunday, March 17, 2013

Construtores na Orientação a Objetos

Construtores

Quando implementamos nossas classes em uma linguagem de programação Orientada a Objetos, o compilador, por padrão, cria um método sem parâmetros chamado “construtor”. A finalidade desse método é executar atividades iniciais que preparem o objeto para uso como: invocar o construtor da classe base, atribuir valores padrões para as variáveis membros; etc.


O (pseudo-) método construtor determina que ações devem ser executadas quando da criação de um objeto. Em Java, o construtor é definido como um método cujo nome deve ser o mesmo nome da classe e sem indicação do tipo de retorno -- nem mesmo void. O construtor é unicamente invocado no momento da criação do objeto através do operador new.

O retorno do operador new é uma referência para o objeto recém-criado. O construtor pode receber argumentos, como qualquer método. Usando o mecanismo de sobrecarga, mais de um construtor pode ser definido para uma classe. (RICARTE)

Uma das principais características da Orientação a Objetos, o Polimorfismo, permite que o método construtor, como qualquer método, seja sobrescrito e tenha várias versões com assinaturas diferentes (parâmetros com tipos e quantidades diversas). Vejamos mais detalhes no texto abaixo:

Toda classe tem pelo menos um construtor sempre definido. Se nenhum construtor for explicitamente definido pelo programador da classe, um construtor padrão, que não recebe argumentos, é incluído para a classe pelo compilador Java. No entanto, se o programador da classe criar pelo menos um método construtor, o construtor padrão não será criado automaticamente -- se o programador desejar mantê-lo, deverá criar um construtor sem argumentos explicitamente.

No momento em que um construtor é invocado, a seguinte sequência de ações é executada para a criação de um objeto:

  1. O espaço para o objeto é alocado e seu conteúdo é inicializado (bitwise) com zeros.
  2. O construtor da classe base é invocado. Se a classe não tem uma superclasse definida explicitamente, a classe Object é a classe base.
  3. Os membros da classe são inicializados para o objeto, seguindo a ordem em que foram declarados na classe.
  4. O restante do corpo do construtor é executado.

    Seguir essa sequência é uma necessidade de forma a garantir que, quando o corpo de um construtor esteja sendo executado, o objeto já terá à disposição as funcionalidades mínimas necessárias, quais sejam aquelas definidas por seus ancestrais. O primeiro passo garante que nenhum campo do objeto terá um valor arbitrário, que possa tornar erros de não inicialização difíceis de detectar.

    Bibliografia
     

    RICARTE, I. L. M. Construtores. In: <http://www.dca.fee.unicamp.br/cursos/PooJava/metodos/construtor.html>. Acesso em 17/03/2013.

     

    Thursday, March 14, 2013

    Mensagens na Orientação a Objetos


    Mensagens na Orientação a Objetos



    A orientação a objetos é um paradigma de análise, projeto e programação de sistemas de software baseado na composição e interação entre diversas unidades de software chamadas de objetos (WIKIPÉDIA).

    A interação entre esses objetos se dá por meio de trocas de mensagens. Por definição, “Mensagem é uma chamada a um objeto para invocar um de seus métodos, ativando um comportamento descrito por sua classe. Também pode ser direcionada diretamente a uma classe (através de uma invocação a um método estático).” (WIKIPÉDIA).

    Em outras palavras, mensagem é um texto enviado de um objeto para outro, que este entende como invocação de um de seus métodos (habilidades ou comportamentos). Além desse texto em si, é possível, também, a passagem de valores ou parâmetros, que são informações trocadas entre os objetos.

    Se uma mensagem não é compreendida pelo objeto, ele automaticamente retornará um erro. Uma mesma mensagem pode executar comportamentos diferentes, dependendo do contexto. Isso envolve mais uma característica da Orientação a Objetos: o Polimorfismo.

    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: 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);
    • Paramétrico: se restringe ao uso de templates (C++, por exemplo) e generics (Java/C♯);
    • Ad-Hoc: Sobrecarga: duas funções/métodos com o mesmo nome mas assinaturas diferentes;
    • 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); (WIKIPÉDIA)


    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=34261231>. Acesso em: 12 mar. 2013.

    DAVID, M.F. Programação Orientada A Objetos: Uma Introdução. In < http://www.hardware.com.br/artigos/programacao-orientada-objetos/>. Acesso em 12 mar. 2013.

     

    Search This Blog

    About Me

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